Passed
Push — master ( f96fe0...0a59ee )
by Lars
04:21
created

UTF8   F

Complexity

Total Complexity 1730

Size/Duplication

Total Lines 13983
Duplicated Lines 0 %

Test Coverage

Coverage 81.83%

Importance

Changes 102
Bugs 53 Features 6
Metric Value
eloc 4426
c 102
b 53
f 6
dl 0
loc 13983
ccs 3194
cts 3903
cp 0.8183
rs 0.8
wmc 1730

307 Methods

Rating   Name   Duplication   Size   Complexity  
A add_bom_to_string() 0 7 2
A array_change_key_case() 0 23 5
A __construct() 0 2 1
B between() 0 48 8
A char_at() 0 7 2
A access() 0 11 4
A callback() 0 3 1
A binary_to_str() 0 12 3
A bom() 0 3 1
B chr_to_decimal() 0 38 8
A str_substr_after_first_separator() 0 28 6
A file_has_bom() 0 8 2
A str_begins() 0 3 1
A max() 0 14 3
B str_camelize() 0 74 10
A parse_str() 0 18 4
A filter_input() 0 16 3
A str_contains() 0 10 2
B str_to_lines() 0 29 8
A substr_in_byte() 0 18 6
A stripos_in_byte() 0 12 4
A get_unique_string() 0 22 3
A is_bom() 0 10 3
A is_hexadecimal() 0 8 2
A strnatcasecmp() 0 5 1
A encode_mimeheader() 0 26 5
A substr_left() 0 15 4
A count_chars() 0 11 1
D strlen() 0 102 19
A str_isubstr_last() 0 25 4
A to_int() 0 7 2
A ctype_loaded() 0 3 1
A str_replace_beginning() 0 24 6
A has_uppercase() 0 8 2
A remove_left() 0 24 4
B stripos() 0 59 11
A str_offset_exists() 0 10 2
D strrchr() 0 104 20
A to_filename() 0 9 1
A str_iends_with() 0 11 3
A max_chr_width() 0 8 2
A isBinary() 0 3 1
C utf8_decode() 0 61 13
A ltrim() 0 27 5
A emoji_decode() 0 18 2
A is_utf8() 0 13 4
A remove_html() 0 3 1
B str_longest_common_suffix() 0 54 10
C wordwrap() 0 68 14
B ucfirst() 0 57 7
A lcword() 0 13 1
A str_pad_both() 0 12 1
A str_index_last() 0 11 1
A str_substr_last() 0 33 6
A mbstring_loaded() 0 3 1
A str_limit() 0 26 6
D chr() 0 109 18
A html_escape() 0 6 1
A toUTF8() 0 3 1
A string() 0 12 3
D normalize_encoding() 0 147 16
B rxClass() 0 45 8
B get_file_type() 0 65 7
A str_ensure_right() 0 13 4
A chr_to_int() 0 3 1
B str_titleize_for_humans() 0 171 7
C is_utf16() 0 71 16
A isHtml() 0 3 1
C filter() 0 51 12
A normalize_whitespace() 0 9 1
A str_starts_with() 0 11 3
A isBase64() 0 3 1
A str_humanize() 0 15 1
A is_html() 0 14 2
C substr_count_in_byte() 0 55 15
A decode_mimeheader() 0 8 3
A html_decode() 0 6 1
A strchr() 0 13 1
A strichr() 0 13 1
A isUtf32() 0 3 1
A str_index_first() 0 11 1
A strlen_in_byte() 0 12 3
A str_ireplace_ending() 0 21 6
A rtrim() 0 27 5
C str_longest_common_substring() 0 76 16
A regex_replace() 0 20 3
A chunk_split() 0 3 1
A titlecase() 0 35 5
A getData() 0 6 1
A str_iindex_first() 0 11 1
B strtolower() 0 60 10
B urldecode() 0 51 8
A str_isubstr_before_first_separator() 0 19 5
B strrev() 0 43 10
A replace_all() 0 11 2
D substr_replace() 0 124 27
A removeBOM() 0 3 1
A strstr_in_byte() 0 15 4
A emoji_encode() 0 18 2
A str_matches_pattern() 0 3 1
A is_alpha() 0 8 2
C str_titleize() 0 69 12
A str_split_array() 0 17 2
A ws() 0 3 1
B get_random_string() 0 56 10
A str_replace_first() 0 20 2
A fix_utf8() 0 30 4
A toLatin1() 0 3 1
A str_pad_right() 0 12 1
B ucwords() 0 51 9
A first_char() 0 14 4
A to_boolean() 0 35 5
C stristr() 0 71 15
A isUtf8() 0 3 1
A strncasecmp() 0 10 1
B strwidth() 0 43 8
A str_iends() 0 3 1
A css_stripe_media_queries() 0 6 1
A trim() 0 27 5
A clean() 0 48 6
A is_serialized() 0 11 3
A str_upper_camelize() 0 8 1
A is_uppercase() 0 8 2
A substr_compare() 0 33 6
C substr_count() 0 65 16
A strnatcmp() 0 9 2
D str_pad() 0 146 16
A urldecode_unicode_helper() 0 8 2
A str_ireplace() 0 18 3
A to_latin1() 0 3 1
A str_replace_ending() 0 24 6
A string_has_bom() 0 10 3
B strtr() 0 42 11
B str_contains_all() 0 24 9
A is_ascii() 0 3 1
A normalize_line_ending() 0 3 1
A str_isubstr_after_last_separator() 0 26 5
D range() 0 72 23
B strspn() 0 30 10
A strcasecmp() 0 21 1
A str_transliterate() 0 6 1
B rawurldecode() 0 51 8
A str_ends() 0 3 1
B str_capitalize_name_helper() 0 86 10
A utf8_encode() 0 16 3
A normalize_msword() 0 3 1
C str_detect_encoding() 0 111 13
A spaces_to_tabs() 0 11 3
A str_istarts_with() 0 11 3
A is_blank() 0 8 2
A str_replace() 0 14 1
A substr_iright() 0 15 4
D getCharDirection() 0 105 118
A htmlspecialchars() 0 15 3
A replace() 0 11 2
A filter_var_array() 0 15 2
A decimal_to_chr() 0 3 1
A to_iso8859() 0 16 4
A has_whitespace() 0 8 2
A words_limit() 0 20 5
A strip_tags() 0 18 4
A pcre_utf8_support() 0 4 1
A str_isubstr_before_last_separator() 0 24 6
D str_truncate_safe() 0 86 18
A codepoints() 0 36 5
A substr_right() 0 31 6
A lowerCaseFirst() 0 13 1
D str_split() 0 138 29
A str_ends_with_any() 0 13 4
A chr_map() 0 5 1
A strrpos_in_byte() 0 12 4
A cleanup() 0 25 2
A remove_right() 0 25 4
F strrpos() 0 122 25
A remove_html_breaks() 0 3 1
A showSupport() 0 17 3
A remove_invisible_characters() 0 9 1
A single_chr_html_encode() 0 18 4
A chars() 0 4 1
A str_replace_last() 0 19 2
A str_iindex_last() 0 11 1
A str_substr_before_last_separator() 0 31 6
B is_binary() 0 38 9
A intlChar_loaded() 0 3 1
B strtocasefold() 0 33 7
A lcfirst() 0 44 5
A tabs_to_spaces() 0 11 3
B is_url() 0 44 7
A finfo_loaded() 0 3 1
B str_truncate() 0 44 7
D strripos() 0 99 19
A strpos_in_byte() 0 12 4
A str_ends_with() 0 11 3
A fits_inside() 0 3 1
A to_ascii() 0 6 1
A is_binary_file() 0 16 3
A intl_loaded() 0 3 1
A reduce_string_array() 0 29 6
B str_longest_common_prefix() 0 51 8
A mbstring_overloaded() 0 11 2
A str_pad_left() 0 12 1
A str_substr_first() 0 33 6
A html_stripe_empty_tags() 0 6 1
A chr_size_list() 0 17 3
A remove_bom() 0 22 5
A str_repeat() 0 5 1
A strpbrk() 0 11 4
A whitespace_table() 0 3 1
A substr_count_simple() 0 31 6
A str_iends_with_any() 0 13 4
A str_substr_after_last_separator() 0 28 6
A str_isubstr_after_first_separator() 0 26 5
F extract_text() 0 175 34
A json_loaded() 0 3 1
A isBom() 0 3 1
B str_snakeize() 0 57 6
A int_to_chr() 0 3 1
A is_lowercase() 0 8 2
A str_sort() 0 15 3
D to_utf8() 0 119 35
A ucword() 0 6 1
A str_underscored() 0 3 1
A str_offset_get() 0 14 4
A strip_whitespace() 0 7 2
A hasBom() 0 3 1
A toAscii() 0 6 1
A str_ibegins() 0 3 1
A str_capitalize_name() 0 8 1
B str_limit_after_word() 0 53 11
A iconv_loaded() 0 3 1
A lcwords() 0 34 6
A str_upper_first() 0 13 1
A isAscii() 0 3 1
A normalizeEncoding() 0 3 1
A swapCase() 0 17 4
A filter_var() 0 15 2
A substr_ileft() 0 15 4
A is_empty() 0 3 1
B html_encode() 0 53 11
A str_dasherize() 0 3 1
A isUtf16() 0 3 1
A str_ensure_left() 0 11 3
F encode() 0 143 37
B urldecode_fix_win1252_chars() 0 227 1
A toIso8859() 0 3 1
C is_utf32() 0 71 16
C ord() 0 77 16
B to_string() 0 26 7
A is_alphanumeric() 0 8 2
A strtonatfold() 0 7 1
A json_decode() 0 14 2
A fix_simple_utf8() 0 32 4
C strcspn() 0 52 12
A checkForSupport() 0 47 4
A is_printable() 0 3 1
B is_json() 0 27 8
A fixStrCaseHelper() 0 41 5
A int_to_hex() 0 7 2
B str_split_pattern() 0 49 11
D strstr() 0 95 18
A has_lowercase() 0 8 2
A json_encode() 0 10 2
A str_isubstr_first() 0 25 4
A is_base64() 0 20 5
A str_last_char() 0 16 4
A str_ireplace_beginning() 0 21 6
A hex_to_int() 0 14 3
A htmlentities() 0 28 3
A hex_to_chr() 0 3 1
A str_substr_before_first_separator() 0 32 6
F substr() 0 142 32
A isJson() 0 3 1
A wordwrap_per_line() 0 28 5
A str_surround() 0 3 1
A strncmp() 0 19 4
A filter_input_array() 0 15 3
A str_insert() 0 28 4
A getSupportInfo() 0 13 3
A utf8_fix_win1252_chars() 0 3 1
A replace_diamond_question_mark() 0 41 5
A chr_to_hex() 0 11 3
D is_utf8_string() 0 134 28
A to_utf8_convert_helper() 0 28 5
B str_delimit() 0 33 8
B strtoupper() 0 60 10
A min() 0 14 3
A is_punctuation() 0 3 1
A collapse_whitespace() 0 8 2
C html_entity_decode() 0 58 13
A str_starts_with_any() 0 17 5
B strrichr() 0 54 11
A split() 0 7 1
A str_istarts_with_any() 0 17 5
B str_contains_any() 0 29 8
A initEmojiData() 0 29 4
A remove_duplicates() 0 16 4
B str_slice() 0 33 10
F strpos() 0 134 27
A str_shuffle() 0 35 6
A strcmp() 0 9 2
B file_get_contents() 0 56 11
A str_word_count() 0 23 5
A strripos_in_byte() 0 12 4
A str_to_binary() 0 10 2
A symfony_polyfill_used() 0 16 5
B str_to_words() 0 36 8
A emoji_from_country_code() 0 17 3

How to fix   Complexity   

Complex Class

Complex classes like UTF8 often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use UTF8, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\helper;
6
7
/**
8
 * @psalm-immutable
9
 */
10
final class UTF8
11
{
12
    /**
13
     * (CRLF|([ZWNJ-ZWJ]|T+|L*(LV?V+|LV|LVT)T*|L+|[^Control])[Extend]*|[Control])
14
     * This regular expression is a work around for http://bugs.exim.org/1279
15
     *
16
     * @deprecated <p>please don't use it anymore</p>
17
     */
18
    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}])";
19
20
    /**
21
     * Bom => Byte-Length
22
     *
23
     * INFO: https://en.wikipedia.org/wiki/Byte_order_mark
24
     *
25
     * @var array<string, int>
26
     */
27
    private static $BOM = [
28
        "\xef\xbb\xbf"     => 3, // UTF-8 BOM
29
        ''              => 6, // UTF-8 BOM as "WINDOWS-1252" (one char has [maybe] more then one byte ...)
30
        "\x00\x00\xfe\xff" => 4, // UTF-32 (BE) BOM
31
        '  þÿ'             => 6, // UTF-32 (BE) BOM as "WINDOWS-1252"
32
        "\xff\xfe\x00\x00" => 4, // UTF-32 (LE) BOM
33
        'ÿþ  '             => 6, // UTF-32 (LE) BOM as "WINDOWS-1252"
34
        "\xfe\xff"         => 2, // UTF-16 (BE) BOM
35
        'þÿ'               => 4, // UTF-16 (BE) BOM as "WINDOWS-1252"
36
        "\xff\xfe"         => 2, // UTF-16 (LE) BOM
37
        'ÿþ'               => 4, // UTF-16 (LE) BOM as "WINDOWS-1252"
38
    ];
39
40
    /**
41
     * Numeric code point => UTF-8 Character
42
     *
43
     * url: http://www.w3schools.com/charsets/ref_utf_punctuation.asp
44
     *
45
     * @var array<int, string>
46
     */
47
    private static $WHITESPACE = [
48
        // NULL Byte
49
        0 => "\x0",
50
        // Tab
51
        9 => "\x9",
52
        // New Line
53
        10 => "\xa",
54
        // Vertical Tab
55
        11 => "\xb",
56
        // Carriage Return
57
        13 => "\xd",
58
        // Ordinary Space
59
        32 => "\x20",
60
        // NO-BREAK SPACE
61
        160 => "\xc2\xa0",
62
        // OGHAM SPACE MARK
63
        5760 => "\xe1\x9a\x80",
64
        // MONGOLIAN VOWEL SEPARATOR
65
        6158 => "\xe1\xa0\x8e",
66
        // EN QUAD
67
        8192 => "\xe2\x80\x80",
68
        // EM QUAD
69
        8193 => "\xe2\x80\x81",
70
        // EN SPACE
71
        8194 => "\xe2\x80\x82",
72
        // EM SPACE
73
        8195 => "\xe2\x80\x83",
74
        // THREE-PER-EM SPACE
75
        8196 => "\xe2\x80\x84",
76
        // FOUR-PER-EM SPACE
77
        8197 => "\xe2\x80\x85",
78
        // SIX-PER-EM SPACE
79
        8198 => "\xe2\x80\x86",
80
        // FIGURE SPACE
81
        8199 => "\xe2\x80\x87",
82
        // PUNCTUATION SPACE
83
        8200 => "\xe2\x80\x88",
84
        // THIN SPACE
85
        8201 => "\xe2\x80\x89",
86
        // HAIR SPACE
87
        8202 => "\xe2\x80\x8a",
88
        // LINE SEPARATOR
89
        8232 => "\xe2\x80\xa8",
90
        // PARAGRAPH SEPARATOR
91
        8233 => "\xe2\x80\xa9",
92
        // NARROW NO-BREAK SPACE
93
        8239 => "\xe2\x80\xaf",
94
        // MEDIUM MATHEMATICAL SPACE
95
        8287 => "\xe2\x81\x9f",
96
        // HALFWIDTH HANGUL FILLER
97
        65440 => "\xef\xbe\xa0",
98
        // IDEOGRAPHIC SPACE
99
        12288 => "\xe3\x80\x80",
100
    ];
101
102
    /**
103
     * @var array<string, string>
104
     */
105
    private static $WHITESPACE_TABLE = [
106
        'SPACE'                     => "\x20",
107
        'NO-BREAK SPACE'            => "\xc2\xa0",
108
        'OGHAM SPACE MARK'          => "\xe1\x9a\x80",
109
        'EN QUAD'                   => "\xe2\x80\x80",
110
        'EM QUAD'                   => "\xe2\x80\x81",
111
        'EN SPACE'                  => "\xe2\x80\x82",
112
        'EM SPACE'                  => "\xe2\x80\x83",
113
        'THREE-PER-EM SPACE'        => "\xe2\x80\x84",
114
        'FOUR-PER-EM SPACE'         => "\xe2\x80\x85",
115
        'SIX-PER-EM SPACE'          => "\xe2\x80\x86",
116
        'FIGURE SPACE'              => "\xe2\x80\x87",
117
        'PUNCTUATION SPACE'         => "\xe2\x80\x88",
118
        'THIN SPACE'                => "\xe2\x80\x89",
119
        'HAIR SPACE'                => "\xe2\x80\x8a",
120
        'LINE SEPARATOR'            => "\xe2\x80\xa8",
121
        'PARAGRAPH SEPARATOR'       => "\xe2\x80\xa9",
122
        'ZERO WIDTH SPACE'          => "\xe2\x80\x8b",
123
        'NARROW NO-BREAK SPACE'     => "\xe2\x80\xaf",
124
        'MEDIUM MATHEMATICAL SPACE' => "\xe2\x81\x9f",
125
        'IDEOGRAPHIC SPACE'         => "\xe3\x80\x80",
126
        'HALFWIDTH HANGUL FILLER'   => "\xef\xbe\xa0",
127
    ];
128
129
    /**
130
     * @var array{upper: string[], lower: string[]}
131
     */
132
    private static $COMMON_CASE_FOLD = [
133
        'upper' => [
134
            'µ',
135
            'ſ',
136
            "\xCD\x85",
137
            'ς',
138
            'ẞ',
139
            "\xCF\x90",
140
            "\xCF\x91",
141
            "\xCF\x95",
142
            "\xCF\x96",
143
            "\xCF\xB0",
144
            "\xCF\xB1",
145
            "\xCF\xB5",
146
            "\xE1\xBA\x9B",
147
            "\xE1\xBE\xBE",
148
        ],
149
        'lower' => [
150
            'μ',
151
            's',
152
            'ι',
153
            'σ',
154
            'ß',
155
            'β',
156
            'θ',
157
            'φ',
158
            'π',
159
            'κ',
160
            'ρ',
161
            'ε',
162
            "\xE1\xB9\xA1",
163
            'ι',
164
        ],
165
    ];
166
167
    /**
168
     * @var array<string, mixed>
169
     */
170
    private static $SUPPORT = [];
171
172
    /**
173
     * @var array<string, string>|null
174
     */
175
    private static $BROKEN_UTF8_FIX;
176
177
    /**
178
     * @var array<int, string>|null
179
     */
180
    private static $WIN1252_TO_UTF8;
181
182
    /**
183
     * @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...
184
     */
185
    private static $INTL_TRANSLITERATOR_LIST;
186
187
    /**
188
     * @var array<string>|null
189
     */
190
    private static $ENCODINGS;
191
192
    /**
193
     * @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...
194
     */
195
    private static $ORD;
196
197
    /**
198
     * @var array<string, string>|null
199
     */
200
    private static $EMOJI;
201
202
    /**
203
     * @var array<string>|null
204
     */
205
    private static $EMOJI_VALUES_CACHE;
206
207
    /**
208
     * @var array<string>|null
209
     */
210
    private static $EMOJI_KEYS_CACHE;
211
212
    /**
213
     * @var array<string>|null
214
     */
215
    private static $EMOJI_KEYS_REVERSIBLE_CACHE;
216
217
    /**
218
     * @var array<int, string>|null
219
     */
220
    private static $CHR;
221
222
    /**
223
     * __construct()
224
     */
225 34
    public function __construct()
226
    {
227 34
    }
228
229
    /**
230
     * Return the character at the specified position: $str[1] like functionality.
231
     *
232
     * @param string $str      <p>A UTF-8 string.</p>
233
     * @param int    $pos      <p>The position of character to return.</p>
234
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
235
     *
236
     * @psalm-pure
237
     *
238
     * @return string
239
     *                <p>Single multi-byte character.</p>
240
     */
241 3
    public static function access(string $str, int $pos, string $encoding = 'UTF-8'): string
242
    {
243 3
        if ($str === '' || $pos < 0) {
244 2
            return '';
245
        }
246
247 3
        if ($encoding === 'UTF-8') {
248 3
            return (string) \mb_substr($str, $pos, 1);
249
        }
250
251
        return (string) self::substr($str, $pos, 1, $encoding);
252
    }
253
254
    /**
255
     * Prepends UTF-8 BOM character to the string and returns the whole string.
256
     *
257
     * INFO: If BOM already existed there, the Input string is returned.
258
     *
259
     * @param string $str <p>The input string.</p>
260
     *
261
     * @psalm-pure
262
     *
263
     * @return string
264
     *                <p>The output string that contains BOM.</p>
265
     */
266 2
    public static function add_bom_to_string(string $str): string
267
    {
268 2
        if (!self::string_has_bom($str)) {
269 2
            $str = self::bom() . $str;
270
        }
271
272 2
        return $str;
273
    }
274
275
    /**
276
     * Changes all keys in an array.
277
     *
278
     * @param array<string, mixed> $array    <p>The array to work on</p>
279
     * @param int                  $case     [optional] <p> Either <strong>CASE_UPPER</strong><br>
280
     *                                       or <strong>CASE_LOWER</strong> (default)</p>
281
     * @param string               $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
282
     *
283
     * @psalm-pure
284
     *
285
     * @return string[]
286
     *                  <p>An array with its keys lower- or uppercased.</p>
287
     */
288 2
    public static function array_change_key_case(
289
        array $array,
290
        int $case = \CASE_LOWER,
291
        string $encoding = 'UTF-8'
292
    ): array {
293
        if (
294 2
            $case !== \CASE_LOWER
295
            &&
296 2
            $case !== \CASE_UPPER
297
        ) {
298
            $case = \CASE_LOWER;
299
        }
300
301 2
        $return = [];
302 2
        foreach ($array as $key => &$value) {
303 2
            $key = $case === \CASE_LOWER
304 2
                ? self::strtolower((string) $key, $encoding)
305 2
                : self::strtoupper((string) $key, $encoding);
306
307 2
            $return[$key] = $value;
308
        }
309
310 2
        return $return;
311
    }
312
313
    /**
314
     * Returns the substring between $start and $end, if found, or an empty
315
     * string. An optional offset may be supplied from which to begin the
316
     * search for the start string.
317
     *
318
     * @param string $str
319
     * @param string $start    <p>Delimiter marking the start of the substring.</p>
320
     * @param string $end      <p>Delimiter marking the end of the substring.</p>
321
     * @param int    $offset   [optional] <p>Index from which to begin the search. Default: 0</p>
322
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
323
     *
324
     * @psalm-pure
325
     *
326
     * @return string
327
     */
328 16
    public static function between(
329
        string $str,
330
        string $start,
331
        string $end,
332
        int $offset = 0,
333
        string $encoding = 'UTF-8'
334
    ): string {
335 16
        if ($encoding === 'UTF-8') {
336 8
            $start_position = \mb_strpos($str, $start, $offset);
337 8
            if ($start_position === false) {
338 1
                return '';
339
            }
340
341 7
            $substr_index = $start_position + (int) \mb_strlen($start);
342 7
            $end_position = \mb_strpos($str, $end, $substr_index);
343
            if (
344 7
                $end_position === false
345
                ||
346 7
                $end_position === $substr_index
347
            ) {
348 2
                return '';
349
            }
350
351 5
            return (string) \mb_substr($str, $substr_index, $end_position - $substr_index);
352
        }
353
354 8
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
355
356 8
        $start_position = self::strpos($str, $start, $offset, $encoding);
357 8
        if ($start_position === false) {
358 1
            return '';
359
        }
360
361 7
        $substr_index = $start_position + (int) self::strlen($start, $encoding);
362 7
        $end_position = self::strpos($str, $end, $substr_index, $encoding);
363
        if (
364 7
            $end_position === false
365
            ||
366 7
            $end_position === $substr_index
367
        ) {
368 2
            return '';
369
        }
370
371 5
        return (string) self::substr(
372 5
            $str,
373 5
            $substr_index,
374 5
            $end_position - $substr_index,
375 5
            $encoding
376
        );
377
    }
378
379
    /**
380
     * Convert binary into a string.
381
     *
382
     * @param mixed $bin 1|0
383
     *
384
     * @psalm-pure
385
     *
386
     * @return string
387
     */
388 2
    public static function binary_to_str($bin): string
389
    {
390 2
        if (!isset($bin[0])) {
391
            return '';
392
        }
393
394 2
        $convert = \base_convert($bin, 2, 16);
395 2
        if ($convert === '0') {
396 1
            return '';
397
        }
398
399 2
        return \pack('H*', $convert);
400
    }
401
402
    /**
403
     * Returns the UTF-8 Byte Order Mark Character.
404
     *
405
     * INFO: take a look at UTF8::$bom for e.g. UTF-16 and UTF-32 BOM values
406
     *
407
     * @psalm-pure
408
     *
409
     * @return string
410
     *                <p>UTF-8 Byte Order Mark.</p>
411
     */
412 4
    public static function bom(): string
413
    {
414 4
        return "\xef\xbb\xbf";
415
    }
416
417
    /**
418
     * @alias of UTF8::chr_map()
419
     *
420
     * @param callable $callback
421
     * @param string   $str
422
     *
423
     * @psalm-pure
424
     *
425
     * @return string[]
426
     *
427
     * @see   UTF8::chr_map()
428
     */
429 2
    public static function callback($callback, string $str): array
430
    {
431 2
        return self::chr_map($callback, $str);
432
    }
433
434
    /**
435
     * Returns the character at $index, with indexes starting at 0.
436
     *
437
     * @param string $str      <p>The input string.</p>
438
     * @param int    $index    <p>Position of the character.</p>
439
     * @param string $encoding [optional] <p>Default is UTF-8</p>
440
     *
441
     * @psalm-pure
442
     *
443
     * @return string
444
     *                <p>The character at $index.</p>
445
     */
446 9
    public static function char_at(string $str, int $index, string $encoding = 'UTF-8'): string
447
    {
448 9
        if ($encoding === 'UTF-8') {
449 5
            return (string) \mb_substr($str, $index, 1);
450
        }
451
452 4
        return (string) self::substr($str, $index, 1, $encoding);
453
    }
454
455
    /**
456
     * Returns an array consisting of the characters in the string.
457
     *
458
     * @param string $str <p>The input string.</p>
459
     *
460
     * @psalm-pure
461
     *
462
     * @return string[]
463
     *                  <p>An array of chars.</p>
464
     */
465 3
    public static function chars(string $str): array
466
    {
467
        /** @var string[] */
468 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...
469
    }
470
471
    /**
472
     * This method will auto-detect your server environment for UTF-8 support.
473
     *
474
     * @return true|null
475
     *
476
     * @internal <p>You don't need to run it manually, it will be triggered if it's needed.</p>
477
     */
478 5
    public static function checkForSupport()
479
    {
480 5
        if (!isset(self::$SUPPORT['already_checked_via_portable_utf8'])) {
481
            self::$SUPPORT['already_checked_via_portable_utf8'] = true;
482
483
            // http://php.net/manual/en/book.mbstring.php
484
            self::$SUPPORT['mbstring'] = self::mbstring_loaded();
485
            self::$SUPPORT['mbstring_func_overload'] = self::mbstring_overloaded();
486
            if (self::$SUPPORT['mbstring'] === true) {
487
                \mb_internal_encoding('UTF-8');
488
                /** @noinspection UnusedFunctionResultInspection */
489
                /** @noinspection PhpComposerExtensionStubsInspection */
490
                \mb_regex_encoding('UTF-8');
491
                self::$SUPPORT['mbstring_internal_encoding'] = 'UTF-8';
492
            }
493
494
            // http://php.net/manual/en/book.iconv.php
495
            self::$SUPPORT['iconv'] = self::iconv_loaded();
496
497
            // http://php.net/manual/en/book.intl.php
498
            self::$SUPPORT['intl'] = self::intl_loaded();
499
500
            // http://php.net/manual/en/class.intlchar.php
501
            self::$SUPPORT['intlChar'] = self::intlChar_loaded();
502
503
            // http://php.net/manual/en/book.ctype.php
504
            self::$SUPPORT['ctype'] = self::ctype_loaded();
505
506
            // http://php.net/manual/en/class.finfo.php
507
            self::$SUPPORT['finfo'] = self::finfo_loaded();
508
509
            // http://php.net/manual/en/book.json.php
510
            self::$SUPPORT['json'] = self::json_loaded();
511
512
            // http://php.net/manual/en/book.pcre.php
513
            self::$SUPPORT['pcre_utf8'] = self::pcre_utf8_support();
514
515
            self::$SUPPORT['symfony_polyfill_used'] = self::symfony_polyfill_used();
516
            if (self::$SUPPORT['symfony_polyfill_used'] === true) {
517
                \mb_internal_encoding('UTF-8');
518
                self::$SUPPORT['mbstring_internal_encoding'] = 'UTF-8';
519
            }
520
521
            return true;
522
        }
523
524 5
        return null;
525
    }
526
527
    /**
528
     * Generates a UTF-8 encoded character from the given code point.
529
     *
530
     * INFO: opposite to UTF8::ord()
531
     *
532
     * @param int|string $code_point <p>The code point for which to generate a character.</p>
533
     * @param string     $encoding   [optional] <p>Default is UTF-8</p>
534
     *
535
     * @psalm-pure
536
     *
537
     * @return string|null
538
     *                     <p>Multi-byte character, returns null on failure or empty input.</p>
539
     */
540 22
    public static function chr($code_point, string $encoding = 'UTF-8')
541
    {
542
        // init
543
        /**
544
         * @psalm-suppress ImpureStaticVariable
545
         *
546
         * @var array<string,string>
547
         */
548 22
        static $CHAR_CACHE = [];
549
550 22
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
551 5
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
552
        }
553
554
        if (
555 22
            $encoding !== 'UTF-8'
556
            &&
557 22
            $encoding !== 'ISO-8859-1'
558
            &&
559 22
            $encoding !== 'WINDOWS-1252'
560
            &&
561 22
            self::$SUPPORT['mbstring'] === false
562
        ) {
563
            /**
564
             * @psalm-suppress ImpureFunctionCall - is is only a warning
565
             */
566
            \trigger_error('UTF8::chr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
567
        }
568
569 22
        $cache_key = $code_point . '_' . $encoding;
570 22
        if (isset($CHAR_CACHE[$cache_key])) {
571 20
            return $CHAR_CACHE[$cache_key];
572
        }
573
574 13
        if ($code_point <= 127) { // use "simple"-char only until "\x80"
575
576 12
            if (self::$CHR === null) {
577
                self::$CHR = self::getData('chr');
578
            }
579
580
            /**
581
             * @psalm-suppress PossiblyNullArrayAccess
582
             */
583 12
            $chr = self::$CHR[$code_point];
584
585 12
            if ($encoding !== 'UTF-8') {
586 1
                $chr = self::encode($encoding, $chr);
587
            }
588
589 12
            return $CHAR_CACHE[$cache_key] = $chr;
590
        }
591
592
        //
593
        // fallback via "IntlChar"
594
        //
595
596 6
        if (self::$SUPPORT['intlChar'] === true) {
597
            /** @noinspection PhpComposerExtensionStubsInspection */
598 6
            $chr = \IntlChar::chr($code_point);
599
600 6
            if ($encoding !== 'UTF-8') {
601
                $chr = self::encode($encoding, $chr);
602
            }
603
604 6
            return $CHAR_CACHE[$cache_key] = $chr;
605
        }
606
607
        //
608
        // fallback via vanilla php
609
        //
610
611
        if (self::$CHR === null) {
612
            self::$CHR = self::getData('chr');
613
        }
614
615
        $code_point = (int) $code_point;
616
        if ($code_point <= 0x7F) {
617
            /**
618
             * @psalm-suppress PossiblyNullArrayAccess
619
             */
620
            $chr = self::$CHR[$code_point];
621
        } elseif ($code_point <= 0x7FF) {
622
            /**
623
             * @psalm-suppress PossiblyNullArrayAccess
624
             */
625
            $chr = self::$CHR[($code_point >> 6) + 0xC0] .
626
                   self::$CHR[($code_point & 0x3F) + 0x80];
627
        } elseif ($code_point <= 0xFFFF) {
628
            /**
629
             * @psalm-suppress PossiblyNullArrayAccess
630
             */
631
            $chr = self::$CHR[($code_point >> 12) + 0xE0] .
632
                   self::$CHR[(($code_point >> 6) & 0x3F) + 0x80] .
633
                   self::$CHR[($code_point & 0x3F) + 0x80];
634
        } else {
635
            /**
636
             * @psalm-suppress PossiblyNullArrayAccess
637
             */
638
            $chr = self::$CHR[($code_point >> 18) + 0xF0] .
639
                   self::$CHR[(($code_point >> 12) & 0x3F) + 0x80] .
640
                   self::$CHR[(($code_point >> 6) & 0x3F) + 0x80] .
641
                   self::$CHR[($code_point & 0x3F) + 0x80];
642
        }
643
644
        if ($encoding !== 'UTF-8') {
645
            $chr = self::encode($encoding, $chr);
646
        }
647
648
        return $CHAR_CACHE[$cache_key] = $chr;
649
    }
650
651
    /**
652
     * Applies callback to all characters of a string.
653
     *
654
     * @param callable $callback <p>The callback function.</p>
655
     * @param string   $str      <p>UTF-8 string to run callback on.</p>
656
     *
657
     * @psalm-pure
658
     *
659
     * @return string[]
660
     *                  <p>The outcome of the callback, as array.</p>
661
     */
662 2
    public static function chr_map($callback, string $str): array
663
    {
664 2
        return \array_map(
665 2
            $callback,
666 2
            self::str_split($str)
667
        );
668
    }
669
670
    /**
671
     * Generates an array of byte length of each character of a Unicode string.
672
     *
673
     * 1 byte => U+0000  - U+007F
674
     * 2 byte => U+0080  - U+07FF
675
     * 3 byte => U+0800  - U+FFFF
676
     * 4 byte => U+10000 - U+10FFFF
677
     *
678
     * @param string $str <p>The original unicode string.</p>
679
     *
680
     * @psalm-pure
681
     *
682
     * @return int[]
683
     *               <p>An array of byte lengths of each character.</p>
684
     */
685 4
    public static function chr_size_list(string $str): array
686
    {
687 4
        if ($str === '') {
688 4
            return [];
689
        }
690
691 4
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
692
            return \array_map(
693
                static function (string $data): int {
694
                    // "mb_" is available if overload is used, so use it ...
695
                    return \mb_strlen($data, 'CP850'); // 8-BIT
696
                },
697
                self::str_split($str)
698
            );
699
        }
700
701 4
        return \array_map('\strlen', self::str_split($str));
702
    }
703
704
    /**
705
     * Get a decimal code representation of a specific character.
706
     *
707
     * @param string $char <p>The input character.</p>
708
     *
709
     * @psalm-pure
710
     *
711
     * @return int
712
     */
713 4
    public static function chr_to_decimal(string $char): int
714
    {
715 4
        if (self::$SUPPORT['iconv'] === true) {
716 4
            $chr_tmp = \iconv('UTF-8', 'UCS-4LE', $char);
717 4
            if ($chr_tmp !== false) {
718
                /** @noinspection OffsetOperationsInspection */
719 4
                return \unpack('V', $chr_tmp)[1];
720
            }
721
        }
722
723
        $code = self::ord($char[0]);
724
        $bytes = 1;
725
726
        if (!($code & 0x80)) {
727
            // 0xxxxxxx
728
            return $code;
729
        }
730
731
        if (($code & 0xe0) === 0xc0) {
732
            // 110xxxxx
733
            $bytes = 2;
734
            $code &= ~0xc0;
735
        } elseif (($code & 0xf0) === 0xe0) {
736
            // 1110xxxx
737
            $bytes = 3;
738
            $code &= ~0xe0;
739
        } elseif (($code & 0xf8) === 0xf0) {
740
            // 11110xxx
741
            $bytes = 4;
742
            $code &= ~0xf0;
743
        }
744
745
        for ($i = 2; $i <= $bytes; ++$i) {
746
            // 10xxxxxx
747
            $code = ($code << 6) + (self::ord($char[$i - 1]) & ~0x80);
748
        }
749
750
        return $code;
751
    }
752
753
    /**
754
     * Get hexadecimal code point (U+xxxx) of a UTF-8 encoded character.
755
     *
756
     * @param int|string $char   <p>The input character</p>
757
     * @param string     $prefix [optional]
758
     *
759
     * @psalm-pure
760
     *
761
     * @return string
762
     *                <p>The code point encoded as U+xxxx.</p>
763
     */
764 2
    public static function chr_to_hex($char, string $prefix = 'U+'): string
765
    {
766 2
        if ($char === '') {
767 2
            return '';
768
        }
769
770 2
        if ($char === '&#0;') {
771 2
            $char = '';
772
        }
773
774 2
        return self::int_to_hex(self::ord((string) $char), $prefix);
775
    }
776
777
    /**
778
     * alias for "UTF8::chr_to_decimal()"
779
     *
780
     * @param string $chr
781
     *
782
     * @psalm-pure
783
     *
784
     * @return int
785
     *
786
     * @see        UTF8::chr_to_decimal()
787
     * @deprecated <p>please use "UTF8::chr_to_decimal()"</p>
788
     */
789 2
    public static function chr_to_int(string $chr): int
790
    {
791 2
        return self::chr_to_decimal($chr);
792
    }
793
794
    /**
795
     * Splits a string into smaller chunks and multiple lines, using the specified line ending character.
796
     *
797
     * @param string $body         <p>The original string to be split.</p>
798
     * @param int    $chunk_length [optional] <p>The maximum character length of a chunk.</p>
799
     * @param string $end          [optional] <p>The character(s) to be inserted at the end of each chunk.</p>
800
     *
801
     * @psalm-pure
802
     *
803
     * @return string
804
     *                <p>The chunked string.</p>
805
     */
806 4
    public static function chunk_split(string $body, int $chunk_length = 76, string $end = "\r\n"): string
807
    {
808 4
        return \implode($end, self::str_split($body, $chunk_length));
809
    }
810
811
    /**
812
     * Accepts a string and removes all non-UTF-8 characters from it + extras if needed.
813
     *
814
     * @param string $str                                     <p>The string to be sanitized.</p>
815
     * @param bool   $remove_bom                              [optional] <p>Set to true, if you need to remove
816
     *                                                        UTF-BOM.</p>
817
     * @param bool   $normalize_whitespace                    [optional] <p>Set to true, if you need to normalize the
818
     *                                                        whitespace.</p>
819
     * @param bool   $normalize_msword                        [optional] <p>Set to true, if you need to normalize MS
820
     *                                                        Word chars e.g.: "…"
821
     *                                                        => "..."</p>
822
     * @param bool   $keep_non_breaking_space                 [optional] <p>Set to true, to keep non-breaking-spaces,
823
     *                                                        in
824
     *                                                        combination with
825
     *                                                        $normalize_whitespace</p>
826
     * @param bool   $replace_diamond_question_mark           [optional] <p>Set to true, if you need to remove diamond
827
     *                                                        question mark e.g.: "�"</p>
828
     * @param bool   $remove_invisible_characters             [optional] <p>Set to false, if you not want to remove
829
     *                                                        invisible characters e.g.: "\0"</p>
830
     * @param bool   $remove_invisible_characters_url_encoded [optional] <p>Set to true, if you not want to remove
831
     *                                                        invisible url encoded characters e.g.: "%0B"<br> WARNING:
832
     *                                                        maybe contains false-positives e.g. aa%0Baa -> aaaa.
833
     *                                                        </p>
834
     *
835
     * @psalm-pure
836
     *
837
     * @return string
838
     *                <p>An clean UTF-8 encoded string.</p>
839
     *
840
     * @noinspection PhpTooManyParametersInspection
841
     */
842 88
    public static function clean(
843
        string $str,
844
        bool $remove_bom = false,
845
        bool $normalize_whitespace = false,
846
        bool $normalize_msword = false,
847
        bool $keep_non_breaking_space = false,
848
        bool $replace_diamond_question_mark = false,
849
        bool $remove_invisible_characters = true,
850
        bool $remove_invisible_characters_url_encoded = false
851
    ): string {
852
        // http://stackoverflow.com/questions/1401317/remove-non-utf8-characters-from-string
853
        // caused connection reset problem on larger strings
854
855 88
        $regex = '/
856
          (
857
            (?: [\x00-\x7F]               # single-byte sequences   0xxxxxxx
858
            |   [\xC0-\xDF][\x80-\xBF]    # double-byte sequences   110xxxxx 10xxxxxx
859
            |   [\xE0-\xEF][\x80-\xBF]{2} # triple-byte sequences   1110xxxx 10xxxxxx * 2
860
            |   [\xF0-\xF7][\x80-\xBF]{3} # quadruple-byte sequence 11110xxx 10xxxxxx * 3
861
            ){1,100}                      # ...one or more times
862
          )
863
        | ( [\x80-\xBF] )                 # invalid byte in range 10000000 - 10111111
864
        | ( [\xC0-\xFF] )                 # invalid byte in range 11000000 - 11111111
865
        /x';
866
        /** @noinspection NotOptimalRegularExpressionsInspection */
867 88
        $str = (string) \preg_replace($regex, '$1', $str);
868
869 88
        if ($replace_diamond_question_mark) {
870 33
            $str = self::replace_diamond_question_mark($str, '');
871
        }
872
873 88
        if ($remove_invisible_characters) {
874 88
            $str = self::remove_invisible_characters($str, $remove_invisible_characters_url_encoded);
875
        }
876
877 88
        if ($normalize_whitespace) {
878 37
            $str = self::normalize_whitespace($str, $keep_non_breaking_space);
879
        }
880
881 88
        if ($normalize_msword) {
882 4
            $str = self::normalize_msword($str);
883
        }
884
885 88
        if ($remove_bom) {
886 37
            $str = self::remove_bom($str);
887
        }
888
889 88
        return $str;
890
    }
891
892
    /**
893
     * Clean-up a string and show only printable UTF-8 chars at the end  + fix UTF-8 encoding.
894
     *
895
     * @param string $str <p>The input string.</p>
896
     *
897
     * @psalm-pure
898
     *
899
     * @return string
900
     */
901 33
    public static function cleanup($str): string
902
    {
903
        // init
904 33
        $str = (string) $str;
905
906 33
        if ($str === '') {
907 5
            return '';
908
        }
909
910
        // fixed ISO <-> UTF-8 Errors
911 33
        $str = self::fix_simple_utf8($str);
912
913
        // remove all none UTF-8 symbols
914
        // && remove diamond question mark (�)
915
        // && remove remove invisible characters (e.g. "\0")
916
        // && remove BOM
917
        // && normalize whitespace chars (but keep non-breaking-spaces)
918 33
        return self::clean(
919 33
            $str,
920 33
            true,
921 33
            true,
922 33
            false,
923 33
            true,
924 33
            true,
925 33
            true
926
        );
927
    }
928
929
    /**
930
     * Accepts a string or a array of strings and returns an array of Unicode code points.
931
     *
932
     * INFO: opposite to UTF8::string()
933
     *
934
     * @param string|string[] $arg         <p>A UTF-8 encoded string or an array of such strings.</p>
935
     * @param bool            $use_u_style <p>If True, will return code points in U+xxxx format,
936
     *                                     default, code points will be returned as integers.</p>
937
     *
938
     * @psalm-pure
939
     *
940
     * @return array<int|string>
941
     *                           <p>
942
     *                           The array of code points:<br>
943
     *                           array<int> for $u_style === false<br>
944
     *                           array<string> for $u_style === true<br>
945
     *                           </p>
946
     */
947 12
    public static function codepoints($arg, bool $use_u_style = false): array
948
    {
949 12
        if (\is_string($arg)) {
950 12
            $arg = self::str_split($arg);
951
        }
952
953
        /**
954
         * @psalm-suppress DocblockTypeContradiction
955
         */
956 12
        if (!\is_array($arg)) {
0 ignored issues
show
introduced by
The condition is_array($arg) is always true.
Loading history...
957 4
            return [];
958
        }
959
960 12
        if ($arg === []) {
961 7
            return [];
962
        }
963
964 11
        $arg = \array_map(
965
            [
966 11
                self::class,
967
                'ord',
968
            ],
969 11
            $arg
970
        );
971
972 11
        if ($use_u_style) {
973 2
            $arg = \array_map(
974
                [
975 2
                    self::class,
976
                    'int_to_hex',
977
                ],
978 2
                $arg
979
            );
980
        }
981
982 11
        return $arg;
983
    }
984
985
    /**
986
     * Trims the string and replaces consecutive whitespace characters with a
987
     * single space. This includes tabs and newline characters, as well as
988
     * multibyte whitespace such as the thin space and ideographic space.
989
     *
990
     * @param string $str <p>The input string.</p>
991
     *
992
     * @psalm-pure
993
     *
994
     * @return string
995
     *                <p>A string with trimmed $str and condensed whitespace.</p>
996
     */
997 13
    public static function collapse_whitespace(string $str): string
998
    {
999 13
        if (self::$SUPPORT['mbstring'] === true) {
1000
            /** @noinspection PhpComposerExtensionStubsInspection */
1001 13
            return \trim((string) \mb_ereg_replace('[[:space:]]+', ' ', $str));
1002
        }
1003
1004
        return \trim(self::regex_replace($str, '[[:space:]]+', ' '));
1005
    }
1006
1007
    /**
1008
     * Returns count of characters used in a string.
1009
     *
1010
     * @param string $str                     <p>The input string.</p>
1011
     * @param bool   $clean_utf8              [optional] <p>Remove non UTF-8 chars from the string.</p>
1012
     * @param bool   $try_to_use_mb_functions [optional] <p>Set to false, if you don't want to use
1013
     *
1014
     * @psalm-pure
1015
     *
1016
     * @return int[]
1017
     *               <p>An associative array of Character as keys and
1018
     *               their count as values.</p>
1019
     */
1020 19
    public static function count_chars(
1021
        string $str,
1022
        bool $clean_utf8 = false,
1023
        bool $try_to_use_mb_functions = true
1024
    ): array {
1025 19
        return \array_count_values(
1026 19
            self::str_split(
1027 19
                $str,
1028 19
                1,
1029 19
                $clean_utf8,
1030 19
                $try_to_use_mb_functions
1031
            )
1032
        );
1033
    }
1034
1035
    /**
1036
     * Remove css media-queries.
1037
     *
1038
     * @param string $str
1039
     *
1040
     * @psalm-pure
1041
     *
1042
     * @return string
1043
     */
1044 1
    public static function css_stripe_media_queries(string $str): string
1045
    {
1046 1
        return (string) \preg_replace(
1047 1
            '#@media\\s+(?:only\\s)?(?:[\\s{(]|screen|all)\\s?[^{]+{.*}\\s*}\\s*#isumU',
1048 1
            '',
1049 1
            $str
1050
        );
1051
    }
1052
1053
    /**
1054
     * Checks whether ctype is available on the server.
1055
     *
1056
     * @psalm-pure
1057
     *
1058
     * @return bool
1059
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
1060
     */
1061
    public static function ctype_loaded(): bool
1062
    {
1063
        return \extension_loaded('ctype');
1064
    }
1065
1066
    /**
1067
     * Converts an int value into a UTF-8 character.
1068
     *
1069
     * @param mixed $int
1070
     *
1071
     * @psalm-pure
1072
     *
1073
     * @return string
1074
     */
1075 20
    public static function decimal_to_chr($int): string
1076
    {
1077 20
        return self::html_entity_decode('&#' . $int . ';', \ENT_QUOTES | \ENT_HTML5);
1078
    }
1079
1080
    /**
1081
     * Decodes a MIME header field
1082
     *
1083
     * @param string $str
1084
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
1085
     *
1086
     * @psalm-pure
1087
     *
1088
     * @return false|string
1089
     *                      <p>A decoded MIME field on success,
1090
     *                      or false if an error occurs during the decoding.</p>
1091
     */
1092 2
    public static function decode_mimeheader($str, string $encoding = 'UTF-8')
1093
    {
1094 2
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
1095 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
1096
        }
1097
1098
        // always fallback via symfony polyfill
1099 2
        return \iconv_mime_decode($str, \ICONV_MIME_DECODE_CONTINUE_ON_ERROR, $encoding);
1100
    }
1101
1102
    /**
1103
     * Convert any two-letter country code (ISO 3166-1) to the corresponding Emoji.
1104
     *
1105
     * @see https://en.wikipedia.org/wiki/ISO_3166-1
1106
     *
1107
     * @param string $country_code_iso_3166_1 <p>e.g. DE</p>
1108
     *
1109
     * @return string
1110
     *                <p>Emoji or empty string on error.</p>
1111
     */
1112 1
    public static function emoji_from_country_code(string $country_code_iso_3166_1): string
1113
    {
1114 1
        if ($country_code_iso_3166_1 === '') {
1115 1
            return '';
1116
        }
1117
1118 1
        if (self::strlen($country_code_iso_3166_1) !== 2) {
1119 1
            return '';
1120
        }
1121
1122 1
        $country_code_iso_3166_1 = \strtoupper($country_code_iso_3166_1);
1123
1124 1
        $flagOffset = 0x1F1E6;
1125 1
        $asciiOffset = 0x41;
1126
1127 1
        return self::chr(self::ord($country_code_iso_3166_1[0]) - $asciiOffset + $flagOffset) .
1128 1
               self::chr(self::ord($country_code_iso_3166_1[1]) - $asciiOffset + $flagOffset);
1129
    }
1130
1131
    /**
1132
     * Decodes a string which was encoded by "UTF8::emoji_encode()".
1133
     *
1134
     * @param string $str                            <p>The input string.</p>
1135
     * @param bool   $use_reversible_string_mappings [optional] <p>
1136
     *                                               When <b>TRUE</b>, we se a reversible string mapping
1137
     *                                               between "emoji_encode" and "emoji_decode".</p>
1138
     *
1139
     * @psalm-pure
1140
     *
1141
     * @return string
1142
     */
1143 9
    public static function emoji_decode(
1144
        string $str,
1145
        bool $use_reversible_string_mappings = false
1146
    ): string {
1147 9
        self::initEmojiData();
1148
1149 9
        if ($use_reversible_string_mappings) {
1150 9
            return (string) \str_replace(
1151 9
                (array) self::$EMOJI_KEYS_REVERSIBLE_CACHE,
1152 9
                (array) self::$EMOJI_VALUES_CACHE,
1153 9
                $str
1154
            );
1155
        }
1156
1157 1
        return (string) \str_replace(
1158 1
            (array) self::$EMOJI_KEYS_CACHE,
1159 1
            (array) self::$EMOJI_VALUES_CACHE,
1160 1
            $str
1161
        );
1162
    }
1163
1164
    /**
1165
     * Encode a string with emoji chars into a non-emoji string.
1166
     *
1167
     * @param string $str                            <p>The input string</p>
1168
     * @param bool   $use_reversible_string_mappings [optional] <p>
1169
     *                                               when <b>TRUE</b>, we se a reversible string mapping
1170
     *                                               between "emoji_encode" and "emoji_decode"</p>
1171
     *
1172
     * @psalm-pure
1173
     *
1174
     * @return string
1175
     */
1176 12
    public static function emoji_encode(
1177
        string $str,
1178
        bool $use_reversible_string_mappings = false
1179
    ): string {
1180 12
        self::initEmojiData();
1181
1182 12
        if ($use_reversible_string_mappings) {
1183 9
            return (string) \str_replace(
1184 9
                (array) self::$EMOJI_VALUES_CACHE,
1185 9
                (array) self::$EMOJI_KEYS_REVERSIBLE_CACHE,
1186 9
                $str
1187
            );
1188
        }
1189
1190 4
        return (string) \str_replace(
1191 4
            (array) self::$EMOJI_VALUES_CACHE,
1192 4
            (array) self::$EMOJI_KEYS_CACHE,
1193 4
            $str
1194
        );
1195
    }
1196
1197
    /**
1198
     * Encode a string with a new charset-encoding.
1199
     *
1200
     * INFO:  This function will also try to fix broken / double encoding,
1201
     *        so you can call this function also on a UTF-8 string and you don't mess up the string.
1202
     *
1203
     * @param string $to_encoding                   <p>e.g. 'UTF-16', 'UTF-8', 'ISO-8859-1', etc.</p>
1204
     * @param string $str                           <p>The input string</p>
1205
     * @param bool   $auto_detect_the_from_encoding [optional] <p>Force the new encoding (we try to fix broken / double
1206
     *                                              encoding for UTF-8)<br> otherwise we auto-detect the current
1207
     *                                              string-encoding</p>
1208
     * @param string $from_encoding                 [optional] <p>e.g. 'UTF-16', 'UTF-8', 'ISO-8859-1', etc.<br>
1209
     *                                              A empty string will trigger the autodetect anyway.</p>
1210
     *
1211
     * @psalm-pure
1212
     *
1213
     * @return string
1214
     *
1215
     * @psalm-suppress InvalidReturnStatement
1216
     */
1217 28
    public static function encode(
1218
        string $to_encoding,
1219
        string $str,
1220
        bool $auto_detect_the_from_encoding = true,
1221
        string $from_encoding = ''
1222
    ): string {
1223 28
        if ($str === '' || $to_encoding === '') {
1224 13
            return $str;
1225
        }
1226
1227 28
        if ($to_encoding !== 'UTF-8' && $to_encoding !== 'CP850') {
1228 7
            $to_encoding = self::normalize_encoding($to_encoding, 'UTF-8');
1229
        }
1230
1231 28
        if ($from_encoding && $from_encoding !== 'UTF-8' && $from_encoding !== 'CP850') {
1232 2
            $from_encoding = self::normalize_encoding($from_encoding, '');
1233
        }
1234
1235
        if (
1236 28
            $to_encoding
1237
            &&
1238 28
            $from_encoding
1239
            &&
1240 28
            $from_encoding === $to_encoding
1241
        ) {
1242
            return $str;
1243
        }
1244
1245 28
        if ($to_encoding === 'JSON') {
1246 1
            $return = self::json_encode($str);
1247 1
            if ($return === false) {
1248
                throw new \InvalidArgumentException('The input string [' . $str . '] can not be used for json_encode().');
1249
            }
1250
1251 1
            return $return;
1252
        }
1253 28
        if ($from_encoding === 'JSON') {
1254 1
            $str = self::json_decode($str);
1255 1
            $from_encoding = '';
1256
        }
1257
1258 28
        if ($to_encoding === 'BASE64') {
1259 2
            return \base64_encode($str);
1260
        }
1261 28
        if ($from_encoding === 'BASE64') {
1262 2
            $str = \base64_decode($str, true);
1263 2
            $from_encoding = '';
1264
        }
1265
1266 28
        if ($to_encoding === 'HTML-ENTITIES') {
1267 2
            return self::html_encode($str, true, 'UTF-8');
1268
        }
1269 28
        if ($from_encoding === 'HTML-ENTITIES') {
1270 2
            $str = self::html_entity_decode($str, \ENT_COMPAT, 'UTF-8');
1271 2
            $from_encoding = '';
1272
        }
1273
1274 28
        $from_encoding_auto_detected = false;
1275
        if (
1276 28
            $auto_detect_the_from_encoding
1277
            ||
1278 28
            !$from_encoding
1279
        ) {
1280 28
            $from_encoding_auto_detected = self::str_detect_encoding($str);
1281
        }
1282
1283
        // DEBUG
1284
        //var_dump($to_encoding, $from_encoding, $from_encoding_auto_detected, $str, "\n\n");
1285
1286 28
        if ($from_encoding_auto_detected !== false) {
1287
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
1288 24
            $from_encoding = $from_encoding_auto_detected;
1289 7
        } elseif ($auto_detect_the_from_encoding) {
1290
            // fallback for the "autodetect"-mode
1291 7
            return self::to_utf8($str);
1292
        }
1293
1294
        if (
1295 24
            !$from_encoding
1296
            ||
1297 24
            $from_encoding === $to_encoding
1298
        ) {
1299 15
            return $str;
1300
        }
1301
1302
        if (
1303 19
            $to_encoding === 'UTF-8'
1304
            &&
1305
            (
1306 17
                $from_encoding === 'WINDOWS-1252'
1307
                ||
1308 19
                $from_encoding === 'ISO-8859-1'
1309
            )
1310
        ) {
1311 13
            return self::to_utf8($str);
1312
        }
1313
1314
        if (
1315 12
            $to_encoding === 'ISO-8859-1'
1316
            &&
1317
            (
1318 6
                $from_encoding === 'WINDOWS-1252'
1319
                ||
1320 12
                $from_encoding === 'UTF-8'
1321
            )
1322
        ) {
1323 6
            return self::to_iso8859($str);
1324
        }
1325
1326
        if (
1327 10
            $to_encoding !== 'UTF-8'
1328
            &&
1329 10
            $to_encoding !== 'ISO-8859-1'
1330
            &&
1331 10
            $to_encoding !== 'WINDOWS-1252'
1332
            &&
1333 10
            self::$SUPPORT['mbstring'] === false
1334
        ) {
1335
            /**
1336
             * @psalm-suppress ImpureFunctionCall - is is only a warning
1337
             */
1338
            \trigger_error('UTF8::encode() without mbstring cannot handle "' . $to_encoding . '" encoding', \E_USER_WARNING);
1339
        }
1340
1341 10
        if (self::$SUPPORT['mbstring'] === true) {
1342
            // warning: do not use the symfony polyfill here
1343 10
            $str_encoded = \mb_convert_encoding(
1344 10
                $str,
1345 10
                $to_encoding,
1346 10
                $from_encoding
1347
            );
1348
1349 10
            if ($str_encoded) {
1350 10
                return $str_encoded;
1351
            }
1352
        }
1353
1354
        $return = \iconv($from_encoding, $to_encoding, $str);
1355
        if ($return !== false) {
1356
            return $return;
1357
        }
1358
1359
        return $str;
1360
    }
1361
1362
    /**
1363
     * @param string $str
1364
     * @param string $from_charset      [optional] <p>Set the input charset.</p>
1365
     * @param string $to_charset        [optional] <p>Set the output charset.</p>
1366
     * @param string $transfer_encoding [optional] <p>Set the transfer encoding.</p>
1367
     * @param string $linefeed          [optional] <p>Set the used linefeed.</p>
1368
     * @param int    $indent            [optional] <p>Set the max length indent.</p>
1369
     *
1370
     * @psalm-pure
1371
     *
1372
     * @return false|string
1373
     *                      <p>An encoded MIME field on success,
1374
     *                      or false if an error occurs during the encoding.</p>
1375
     */
1376 1
    public static function encode_mimeheader(
1377
        string $str,
1378
        string $from_charset = 'UTF-8',
1379
        string $to_charset = 'UTF-8',
1380
        string $transfer_encoding = 'Q',
1381
        string $linefeed = "\r\n",
1382
        int $indent = 76
1383
    ) {
1384 1
        if ($from_charset !== 'UTF-8' && $from_charset !== 'CP850') {
1385
            $from_charset = self::normalize_encoding($from_charset, 'UTF-8');
1386
        }
1387
1388 1
        if ($to_charset !== 'UTF-8' && $to_charset !== 'CP850') {
1389 1
            $to_charset = self::normalize_encoding($to_charset, 'UTF-8');
1390
        }
1391
1392
        // always fallback via symfony polyfill
1393 1
        return \iconv_mime_encode(
1394 1
            '',
1395 1
            $str,
1396
            [
1397 1
                'scheme'           => $transfer_encoding,
1398 1
                'line-length'      => $indent,
1399 1
                'input-charset'    => $from_charset,
1400 1
                'output-charset'   => $to_charset,
1401 1
                'line-break-chars' => $linefeed,
1402
            ]
1403
        );
1404
    }
1405
1406
    /**
1407
     * Create an extract from a sentence, so if the search-string was found, it try to centered in the output.
1408
     *
1409
     * @param string   $str                       <p>The input string.</p>
1410
     * @param string   $search                    <p>The searched string.</p>
1411
     * @param int|null $length                    [optional] <p>Default: null === text->length / 2</p>
1412
     * @param string   $replacer_for_skipped_text [optional] <p>Default: …</p>
1413
     * @param string   $encoding                  [optional] <p>Set the charset for e.g. "mb_" function</p>
1414
     *
1415
     * @psalm-pure
1416
     *
1417
     * @return string
1418
     */
1419 1
    public static function extract_text(
1420
        string $str,
1421
        string $search = '',
1422
        int $length = null,
1423
        string $replacer_for_skipped_text = '…',
1424
        string $encoding = 'UTF-8'
1425
    ): string {
1426 1
        if ($str === '') {
1427 1
            return '';
1428
        }
1429
1430 1
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
1431
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
1432
        }
1433
1434 1
        $trim_chars = "\t\r\n -_()!~?=+/*\\,.:;\"'[]{}`&";
1435
1436 1
        if ($length === null) {
1437 1
            $length = (int) \round((int) self::strlen($str, $encoding) / 2, 0);
1438
        }
1439
1440 1
        if ($search === '') {
1441 1
            if ($encoding === 'UTF-8') {
1442 1
                if ($length > 0) {
1443 1
                    $string_length = (int) \mb_strlen($str);
1444 1
                    $end = ($length - 1) > $string_length ? $string_length : ($length - 1);
1445
                } else {
1446 1
                    $end = 0;
1447
                }
1448
1449 1
                $pos = (int) \min(
1450 1
                    \mb_strpos($str, ' ', $end),
1451 1
                    \mb_strpos($str, '.', $end)
1452
                );
1453
            } else {
1454
                if ($length > 0) {
1455
                    $string_length = (int) self::strlen($str, $encoding);
1456
                    $end = ($length - 1) > $string_length ? $string_length : ($length - 1);
1457
                } else {
1458
                    $end = 0;
1459
                }
1460
1461
                $pos = (int) \min(
1462
                    self::strpos($str, ' ', $end, $encoding),
1463
                    self::strpos($str, '.', $end, $encoding)
1464
                );
1465
            }
1466
1467 1
            if ($pos) {
1468 1
                if ($encoding === 'UTF-8') {
1469 1
                    $str_sub = \mb_substr($str, 0, $pos);
1470
                } else {
1471
                    $str_sub = self::substr($str, 0, $pos, $encoding);
1472
                }
1473
1474 1
                if ($str_sub === false) {
1475
                    return '';
1476
                }
1477
1478 1
                return \rtrim($str_sub, $trim_chars) . $replacer_for_skipped_text;
1479
            }
1480
1481
            return $str;
1482
        }
1483
1484 1
        if ($encoding === 'UTF-8') {
1485 1
            $word_position = (int) \mb_stripos($str, $search);
1486 1
            $half_side = (int) ($word_position - $length / 2 + (int) \mb_strlen($search) / 2);
1487
        } else {
1488
            $word_position = (int) self::stripos($str, $search, 0, $encoding);
1489
            $half_side = (int) ($word_position - $length / 2 + (int) self::strlen($search, $encoding) / 2);
1490
        }
1491
1492 1
        $pos_start = 0;
1493 1
        if ($half_side > 0) {
1494 1
            if ($encoding === 'UTF-8') {
1495 1
                $half_text = \mb_substr($str, 0, $half_side);
1496
            } else {
1497
                $half_text = self::substr($str, 0, $half_side, $encoding);
1498
            }
1499 1
            if ($half_text !== false) {
1500 1
                if ($encoding === 'UTF-8') {
1501 1
                    $pos_start = (int) \max(
1502 1
                        \mb_strrpos($half_text, ' '),
1503 1
                        \mb_strrpos($half_text, '.')
1504
                    );
1505
                } else {
1506
                    $pos_start = (int) \max(
1507
                        self::strrpos($half_text, ' ', 0, $encoding),
1508
                        self::strrpos($half_text, '.', 0, $encoding)
1509
                    );
1510
                }
1511
            }
1512
        }
1513
1514 1
        if ($word_position && $half_side > 0) {
1515 1
            $offset = $pos_start + $length - 1;
1516 1
            $real_length = (int) self::strlen($str, $encoding);
1517
1518 1
            if ($offset > $real_length) {
1519
                $offset = $real_length;
1520
            }
1521
1522 1
            if ($encoding === 'UTF-8') {
1523 1
                $pos_end = (int) \min(
1524 1
                    \mb_strpos($str, ' ', $offset),
1525 1
                    \mb_strpos($str, '.', $offset)
1526 1
                ) - $pos_start;
1527
            } else {
1528
                $pos_end = (int) \min(
1529
                    self::strpos($str, ' ', $offset, $encoding),
1530
                    self::strpos($str, '.', $offset, $encoding)
1531
                ) - $pos_start;
1532
            }
1533
1534 1
            if (!$pos_end || $pos_end <= 0) {
1535 1
                if ($encoding === 'UTF-8') {
1536 1
                    $str_sub = \mb_substr($str, $pos_start, (int) \mb_strlen($str));
1537
                } else {
1538
                    $str_sub = self::substr($str, $pos_start, (int) self::strlen($str, $encoding), $encoding);
1539
                }
1540 1
                if ($str_sub !== false) {
1541 1
                    $extract = $replacer_for_skipped_text . \ltrim($str_sub, $trim_chars);
1542
                } else {
1543 1
                    $extract = '';
1544
                }
1545
            } else {
1546 1
                if ($encoding === 'UTF-8') {
1547 1
                    $str_sub = \mb_substr($str, $pos_start, $pos_end);
1548
                } else {
1549
                    $str_sub = self::substr($str, $pos_start, $pos_end, $encoding);
1550
                }
1551 1
                if ($str_sub !== false) {
1552 1
                    $extract = $replacer_for_skipped_text . \trim($str_sub, $trim_chars) . $replacer_for_skipped_text;
1553
                } else {
1554 1
                    $extract = '';
1555
                }
1556
            }
1557
        } else {
1558 1
            $offset = $length - 1;
1559 1
            $true_length = (int) self::strlen($str, $encoding);
1560
1561 1
            if ($offset > $true_length) {
1562
                $offset = $true_length;
1563
            }
1564
1565 1
            if ($encoding === 'UTF-8') {
1566 1
                $pos_end = (int) \min(
1567 1
                    \mb_strpos($str, ' ', $offset),
1568 1
                    \mb_strpos($str, '.', $offset)
1569
                );
1570
            } else {
1571
                $pos_end = (int) \min(
1572
                    self::strpos($str, ' ', $offset, $encoding),
1573
                    self::strpos($str, '.', $offset, $encoding)
1574
                );
1575
            }
1576
1577 1
            if ($pos_end) {
1578 1
                if ($encoding === 'UTF-8') {
1579 1
                    $str_sub = \mb_substr($str, 0, $pos_end);
1580
                } else {
1581
                    $str_sub = self::substr($str, 0, $pos_end, $encoding);
1582
                }
1583 1
                if ($str_sub !== false) {
1584 1
                    $extract = \rtrim($str_sub, $trim_chars) . $replacer_for_skipped_text;
1585
                } else {
1586 1
                    $extract = '';
1587
                }
1588
            } else {
1589 1
                $extract = $str;
1590
            }
1591
        }
1592
1593 1
        return $extract;
1594
    }
1595
1596
    /**
1597
     * Reads entire file into a string.
1598
     *
1599
     * WARNING: Do not use UTF-8 Option ($convert_to_utf8) for binary files (e.g.: images) !!!
1600
     *
1601
     * @see http://php.net/manual/en/function.file-get-contents.php
1602
     *
1603
     * @param string        $filename         <p>
1604
     *                                        Name of the file to read.
1605
     *                                        </p>
1606
     * @param bool          $use_include_path [optional] <p>
1607
     *                                        Prior to PHP 5, this parameter is called
1608
     *                                        use_include_path and is a bool.
1609
     *                                        As of PHP 5 the FILE_USE_INCLUDE_PATH can be used
1610
     *                                        to trigger include path
1611
     *                                        search.
1612
     *                                        </p>
1613
     * @param resource|null $context          [optional] <p>
1614
     *                                        A valid context resource created with
1615
     *                                        stream_context_create. If you don't need to use a
1616
     *                                        custom context, you can skip this parameter by &null;.
1617
     *                                        </p>
1618
     * @param int|null      $offset           [optional] <p>
1619
     *                                        The offset where the reading starts.
1620
     *                                        </p>
1621
     * @param int|null      $max_length       [optional] <p>
1622
     *                                        Maximum length of data read. The default is to read until end
1623
     *                                        of file is reached.
1624
     *                                        </p>
1625
     * @param int           $timeout          <p>The time in seconds for the timeout.</p>
1626
     * @param bool          $convert_to_utf8  <strong>WARNING!!!</strong> <p>Maybe you can't use this option for
1627
     *                                        some files, because they used non default utf-8 chars. Binary files
1628
     *                                        like images or pdf will not be converted.</p>
1629
     * @param string        $from_encoding    [optional] <p>e.g. 'UTF-16', 'UTF-8', 'ISO-8859-1', etc.<br>
1630
     *                                        A empty string will trigger the autodetect anyway.</p>
1631
     *
1632
     * @psalm-pure
1633
     *
1634
     * @return false|string
1635
     *                      <p>The function returns the read data as string or <b>false</b> on failure.</p>
1636
     *
1637
     * @noinspection PhpTooManyParametersInspection
1638
     */
1639 12
    public static function file_get_contents(
1640
        string $filename,
1641
        bool $use_include_path = false,
1642
        $context = null,
1643
        int $offset = null,
1644
        int $max_length = null,
1645
        int $timeout = 10,
1646
        bool $convert_to_utf8 = true,
1647
        string $from_encoding = ''
1648
    ) {
1649
        // init
1650 12
        $filename = \filter_var($filename, \FILTER_SANITIZE_STRING);
1651
        /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
1652 12
        if ($filename === false) {
1653
            return false;
1654
        }
1655
1656 12
        if ($timeout && $context === null) {
1657 9
            $context = \stream_context_create(
1658
                [
1659
                    'http' => [
1660 9
                        'timeout' => $timeout,
1661
                    ],
1662
                ]
1663
            );
1664
        }
1665
1666 12
        if ($offset === null) {
1667 12
            $offset = 0;
1668
        }
1669
1670 12
        if (\is_int($max_length)) {
1671 2
            $data = \file_get_contents($filename, $use_include_path, $context, $offset, $max_length);
1672
        } else {
1673 12
            $data = \file_get_contents($filename, $use_include_path, $context, $offset);
1674
        }
1675
1676
        // return false on error
1677 12
        if ($data === false) {
1678
            return false;
1679
        }
1680
1681 12
        if ($convert_to_utf8) {
1682
            if (
1683 12
                !self::is_binary($data, true)
1684
                ||
1685 9
                self::is_utf16($data, false) !== false
1686
                ||
1687 12
                self::is_utf32($data, false) !== false
1688
            ) {
1689 9
                $data = self::encode('UTF-8', $data, false, $from_encoding);
1690 9
                $data = self::cleanup($data);
1691
            }
1692
        }
1693
1694 12
        return $data;
1695
    }
1696
1697
    /**
1698
     * Checks if a file starts with BOM (Byte Order Mark) character.
1699
     *
1700
     * @param string $file_path <p>Path to a valid file.</p>
1701
     *
1702
     * @throws \RuntimeException if file_get_contents() returned false
1703
     *
1704
     * @return bool
1705
     *              <p><strong>true</strong> if the file has BOM at the start, <strong>false</strong> otherwise</p>
1706
     *
1707
     * @psalm-pure
1708
     */
1709 2
    public static function file_has_bom(string $file_path): bool
1710
    {
1711 2
        $file_content = \file_get_contents($file_path);
1712 2
        if ($file_content === false) {
1713
            throw new \RuntimeException('file_get_contents() returned false for:' . $file_path);
1714
        }
1715
1716 2
        return self::string_has_bom($file_content);
1717
    }
1718
1719
    /**
1720
     * Normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1721
     *
1722
     * @param mixed  $var
1723
     * @param int    $normalization_form
1724
     * @param string $leading_combining
1725
     *
1726
     * @psalm-pure
1727
     *
1728
     * @return mixed
1729
     */
1730 62
    public static function filter(
1731
        $var,
1732
        int $normalization_form = \Normalizer::NFC,
1733
        string $leading_combining = '◌'
1734
    ) {
1735 62
        switch (\gettype($var)) {
1736 62
            case 'object':
1737 62
            case 'array':
1738 6
                foreach ($var as $k => &$v) {
1739 6
                    $v = self::filter($v, $normalization_form, $leading_combining);
1740
                }
1741 6
                unset($v);
1742
1743 6
                break;
1744 62
            case 'string':
1745
1746 62
                if (\strpos($var, "\r") !== false) {
1747
                    // Workaround https://bugs.php.net/65732
1748 3
                    $var = self::normalize_line_ending($var);
1749
                }
1750
1751 62
                if (!ASCII::is_ascii($var)) {
1752 32
                    if (\Normalizer::isNormalized($var, $normalization_form)) {
1753 27
                        $n = '-';
1754
                    } else {
1755 12
                        $n = \Normalizer::normalize($var, $normalization_form);
1756
1757 12
                        if (isset($n[0])) {
1758 7
                            $var = $n;
1759
                        } else {
1760 8
                            $var = self::encode('UTF-8', $var, true);
1761
                        }
1762
                    }
1763
1764
                    if (
1765 32
                        $var[0] >= "\x80"
1766
                        &&
1767 32
                        isset($n[0], $leading_combining[0])
1768
                        &&
1769 32
                        \preg_match('/^\\p{Mn}/u', $var)
1770
                    ) {
1771
                        // Prevent leading combining chars
1772
                        // for NFC-safe concatenations.
1773 3
                        $var = $leading_combining . $var;
1774
                    }
1775
                }
1776
1777 62
                break;
1778
        }
1779
1780 62
        return $var;
1781
    }
1782
1783
    /**
1784
     * "filter_input()"-wrapper with normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1785
     *
1786
     * Gets a specific external variable by name and optionally filters it
1787
     *
1788
     * @see http://php.net/manual/en/function.filter-input.php
1789
     *
1790
     * @param int    $type          <p>
1791
     *                              One of <b>INPUT_GET</b>, <b>INPUT_POST</b>,
1792
     *                              <b>INPUT_COOKIE</b>, <b>INPUT_SERVER</b>, or
1793
     *                              <b>INPUT_ENV</b>.
1794
     *                              </p>
1795
     * @param string $variable_name <p>
1796
     *                              Name of a variable to get.
1797
     *                              </p>
1798
     * @param int    $filter        [optional] <p>
1799
     *                              The ID of the filter to apply. The
1800
     *                              manual page lists the available filters.
1801
     *                              </p>
1802
     * @param mixed  $options       [optional] <p>
1803
     *                              Associative array of options or bitwise disjunction of flags. If filter
1804
     *                              accepts options, flags can be provided in "flags" field of array.
1805
     *                              </p>
1806
     *
1807
     * @psalm-pure
1808
     *
1809
     * @return mixed
1810
     *               <p>
1811
     *               Value of the requested variable on success, <b>FALSE</b> if the filter fails, or <b>NULL</b> if the
1812
     *               <i>variable_name</i> variable is not set. If the flag <b>FILTER_NULL_ON_FAILURE</b> is used, it
1813
     *               returns <b>FALSE</b> if the variable is not set and <b>NULL</b> if the filter fails.
1814
     *               </p>
1815
     */
1816
    public static function filter_input(
1817
        int $type,
1818
        string $variable_name,
1819
        int $filter = \FILTER_DEFAULT,
1820
        $options = null
1821
    ) {
1822
        /**
1823
         * @psalm-suppress ImpureFunctionCall - we use func_num_args only for args count matching here
1824
         */
1825
        if ($options === null || \func_num_args() < 4) {
1826
            $var = \filter_input($type, $variable_name, $filter);
1827
        } else {
1828
            $var = \filter_input($type, $variable_name, $filter, $options);
1829
        }
1830
1831
        return self::filter($var);
1832
    }
1833
1834
    /**
1835
     * "filter_input_array()"-wrapper with normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1836
     *
1837
     * Gets external variables and optionally filters them
1838
     *
1839
     * @see http://php.net/manual/en/function.filter-input-array.php
1840
     *
1841
     * @param int   $type       <p>
1842
     *                          One of <b>INPUT_GET</b>, <b>INPUT_POST</b>,
1843
     *                          <b>INPUT_COOKIE</b>, <b>INPUT_SERVER</b>, or
1844
     *                          <b>INPUT_ENV</b>.
1845
     *                          </p>
1846
     * @param mixed $definition [optional] <p>
1847
     *                          An array defining the arguments. A valid key is a string
1848
     *                          containing a variable name and a valid value is either a filter type, or an array
1849
     *                          optionally specifying the filter, flags and options. If the value is an
1850
     *                          array, valid keys are filter which specifies the
1851
     *                          filter type,
1852
     *                          flags which specifies any flags that apply to the
1853
     *                          filter, and options which specifies any options that
1854
     *                          apply to the filter. See the example below for a better understanding.
1855
     *                          </p>
1856
     *                          <p>
1857
     *                          This parameter can be also an integer holding a filter constant. Then all values in the
1858
     *                          input array are filtered by this filter.
1859
     *                          </p>
1860
     * @param bool  $add_empty  [optional] <p>
1861
     *                          Add missing keys as <b>NULL</b> to the return value.
1862
     *                          </p>
1863
     *
1864
     * @psalm-pure
1865
     *
1866
     * @return mixed
1867
     *               <p>
1868
     *               An array containing the values of the requested variables on success, or <b>FALSE</b> on failure.
1869
     *               An array value will be <b>FALSE</b> if the filter fails, or <b>NULL</b> if the variable is not
1870
     *               set. Or if the flag <b>FILTER_NULL_ON_FAILURE</b> is used, it returns <b>FALSE</b> if the variable
1871
     *               is not set and <b>NULL</b> if the filter fails.
1872
     *               </p>
1873
     */
1874
    public static function filter_input_array(
1875
        int $type,
1876
        $definition = null,
1877
        bool $add_empty = true
1878
    ) {
1879
        /**
1880
         * @psalm-suppress ImpureFunctionCall - we use func_num_args only for args count matching here
1881
         */
1882
        if ($definition === null || \func_num_args() < 2) {
1883
            $a = \filter_input_array($type);
1884
        } else {
1885
            $a = \filter_input_array($type, $definition, $add_empty);
1886
        }
1887
1888
        return self::filter($a);
1889
    }
1890
1891
    /**
1892
     * "filter_var()"-wrapper with normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1893
     *
1894
     * Filters a variable with a specified filter
1895
     *
1896
     * @see http://php.net/manual/en/function.filter-var.php
1897
     *
1898
     * @param mixed $variable <p>
1899
     *                        Value to filter.
1900
     *                        </p>
1901
     * @param int   $filter   [optional] <p>
1902
     *                        The ID of the filter to apply. The
1903
     *                        manual page lists the available filters.
1904
     *                        </p>
1905
     * @param mixed $options  [optional] <p>
1906
     *                        Associative array of options or bitwise disjunction of flags. If filter
1907
     *                        accepts options, flags can be provided in "flags" field of array. For
1908
     *                        the "callback" filter, callable type should be passed. The
1909
     *                        callback must accept one argument, the value to be filtered, and return
1910
     *                        the value after filtering/sanitizing it.
1911
     *                        </p>
1912
     *                        <p>
1913
     *                        <code>
1914
     *                        // for filters that accept options, use this format
1915
     *                        $options = array(
1916
     *                        'options' => array(
1917
     *                        'default' => 3, // value to return if the filter fails
1918
     *                        // other options here
1919
     *                        'min_range' => 0
1920
     *                        ),
1921
     *                        'flags' => FILTER_FLAG_ALLOW_OCTAL,
1922
     *                        );
1923
     *                        $var = filter_var('0755', FILTER_VALIDATE_INT, $options);
1924
     *                        // for filter that only accept flags, you can pass them directly
1925
     *                        $var = filter_var('oops', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
1926
     *                        // for filter that only accept flags, you can also pass as an array
1927
     *                        $var = filter_var('oops', FILTER_VALIDATE_BOOLEAN,
1928
     *                        array('flags' => FILTER_NULL_ON_FAILURE));
1929
     *                        // callback validate filter
1930
     *                        function foo($value)
1931
     *                        {
1932
     *                        // Expected format: Surname, GivenNames
1933
     *                        if (strpos($value, ", ") === false) return false;
1934
     *                        list($surname, $givennames) = explode(", ", $value, 2);
1935
     *                        $empty = (empty($surname) || empty($givennames));
1936
     *                        $notstrings = (!is_string($surname) || !is_string($givennames));
1937
     *                        if ($empty || $notstrings) {
1938
     *                        return false;
1939
     *                        } else {
1940
     *                        return $value;
1941
     *                        }
1942
     *                        }
1943
     *                        $var = filter_var('Doe, Jane Sue', FILTER_CALLBACK, array('options' => 'foo'));
1944
     *                        </code>
1945
     *                        </p>
1946
     *
1947
     * @psalm-pure
1948
     *
1949
     * @return mixed
1950
     *               <p>The filtered data, or <b>FALSE</b> if the filter fails.</p>
1951
     */
1952 2
    public static function filter_var(
1953
        $variable,
1954
        int $filter = \FILTER_DEFAULT,
1955
        $options = null
1956
    ) {
1957
        /**
1958
         * @psalm-suppress ImpureFunctionCall - we use func_num_args only for args count matching here
1959
         */
1960 2
        if (\func_num_args() < 3) {
1961 2
            $variable = \filter_var($variable, $filter);
1962
        } else {
1963 2
            $variable = \filter_var($variable, $filter, $options);
1964
        }
1965
1966 2
        return self::filter($variable);
1967
    }
1968
1969
    /**
1970
     * "filter_var_array()"-wrapper with normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1971
     *
1972
     * Gets multiple variables and optionally filters them
1973
     *
1974
     * @see http://php.net/manual/en/function.filter-var-array.php
1975
     *
1976
     * @param array<mixed> $data       <p>
1977
     *                                 An array with string keys containing the data to filter.
1978
     *                                 </p>
1979
     * @param mixed        $definition [optional] <p>
1980
     *                                 An array defining the arguments. A valid key is a string
1981
     *                                 containing a variable name and a valid value is either a
1982
     *                                 filter type, or an
1983
     *                                 array optionally specifying the filter, flags and options.
1984
     *                                 If the value is an array, valid keys are filter
1985
     *                                 which specifies the filter type,
1986
     *                                 flags which specifies any flags that apply to the
1987
     *                                 filter, and options which specifies any options that
1988
     *                                 apply to the filter. See the example below for a better understanding.
1989
     *                                 </p>
1990
     *                                 <p>
1991
     *                                 This parameter can be also an integer holding a filter constant. Then all values
1992
     *                                 in the input array are filtered by this filter.
1993
     *                                 </p>
1994
     * @param bool         $add_empty  [optional] <p>
1995
     *                                 Add missing keys as <b>NULL</b> to the return value.
1996
     *                                 </p>
1997
     *
1998
     * @psalm-pure
1999
     *
2000
     * @return mixed
2001
     *               <p>
2002
     *               An array containing the values of the requested variables on success, or <b>FALSE</b> on failure.
2003
     *               An array value will be <b>FALSE</b> if the filter fails, or <b>NULL</b> if the variable is not
2004
     *               set.
2005
     *               </p>
2006
     */
2007 2
    public static function filter_var_array(
2008
        array $data,
2009
        $definition = null,
2010
        bool $add_empty = true
2011
    ) {
2012
        /**
2013
         * @psalm-suppress ImpureFunctionCall - we use func_num_args only for args count matching here
2014
         */
2015 2
        if (\func_num_args() < 2) {
2016 2
            $a = \filter_var_array($data);
2017
        } else {
2018 2
            $a = \filter_var_array($data, $definition, $add_empty);
2019
        }
2020
2021 2
        return self::filter($a);
2022
    }
2023
2024
    /**
2025
     * Checks whether finfo is available on the server.
2026
     *
2027
     * @psalm-pure
2028
     *
2029
     * @return bool
2030
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
2031
     */
2032
    public static function finfo_loaded(): bool
2033
    {
2034
        return \class_exists('finfo');
2035
    }
2036
2037
    /**
2038
     * Returns the first $n characters of the string.
2039
     *
2040
     * @param string $str      <p>The input string.</p>
2041
     * @param int    $n        <p>Number of characters to retrieve from the start.</p>
2042
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
2043
     *
2044
     * @psalm-pure
2045
     *
2046
     * @return string
2047
     */
2048 13
    public static function first_char(
2049
        string $str,
2050
        int $n = 1,
2051
        string $encoding = 'UTF-8'
2052
    ): string {
2053 13
        if ($str === '' || $n <= 0) {
2054 5
            return '';
2055
        }
2056
2057 8
        if ($encoding === 'UTF-8') {
2058 4
            return (string) \mb_substr($str, 0, $n);
2059
        }
2060
2061 4
        return (string) self::substr($str, 0, $n, $encoding);
2062
    }
2063
2064
    /**
2065
     * Check if the number of Unicode characters isn't greater than the specified integer.
2066
     *
2067
     * @param string $str      the original string to be checked
2068
     * @param int    $box_size the size in number of chars to be checked against string
2069
     *
2070
     * @psalm-pure
2071
     *
2072
     * @return bool
2073
     *              <p><strong>TRUE</strong> if string is less than or equal to $box_size, <strong>FALSE</strong> otherwise.</p>
2074
     */
2075 2
    public static function fits_inside(string $str, int $box_size): bool
2076
    {
2077 2
        return (int) self::strlen($str) <= $box_size;
2078
    }
2079
2080
    /**
2081
     * Try to fix simple broken UTF-8 strings.
2082
     *
2083
     * INFO: Take a look at "UTF8::fix_utf8()" if you need a more advanced fix for broken UTF-8 strings.
2084
     *
2085
     * If you received an UTF-8 string that was converted from Windows-1252 as it was ISO-8859-1
2086
     * (ignoring Windows-1252 chars from 80 to 9F) use this function to fix it.
2087
     * See: http://en.wikipedia.org/wiki/Windows-1252
2088
     *
2089
     * @param string $str <p>The input string</p>
2090
     *
2091
     * @psalm-pure
2092
     *
2093
     * @return string
2094
     */
2095 47
    public static function fix_simple_utf8(string $str): string
2096
    {
2097 47
        if ($str === '') {
2098 4
            return '';
2099
        }
2100
2101
        /**
2102
         * @psalm-suppress ImpureStaticVariable
2103
         *
2104
         * @var array<mixed>|null
2105
         */
2106 47
        static $BROKEN_UTF8_TO_UTF8_KEYS_CACHE = null;
2107
2108
        /**
2109
         * @psalm-suppress ImpureStaticVariable
2110
         *
2111
         * @var array<mixed>|null
2112
         */
2113 47
        static $BROKEN_UTF8_TO_UTF8_VALUES_CACHE = null;
2114
2115 47
        if ($BROKEN_UTF8_TO_UTF8_KEYS_CACHE === null) {
2116 1
            if (self::$BROKEN_UTF8_FIX === null) {
2117 1
                self::$BROKEN_UTF8_FIX = self::getData('utf8_fix');
2118
            }
2119
2120 1
            $BROKEN_UTF8_TO_UTF8_KEYS_CACHE = \array_keys(self::$BROKEN_UTF8_FIX);
2121 1
            $BROKEN_UTF8_TO_UTF8_VALUES_CACHE = self::$BROKEN_UTF8_FIX;
2122
        }
2123
2124 47
        \assert(\is_array($BROKEN_UTF8_TO_UTF8_VALUES_CACHE));
2125
2126 47
        return \str_replace($BROKEN_UTF8_TO_UTF8_KEYS_CACHE, $BROKEN_UTF8_TO_UTF8_VALUES_CACHE, $str);
2127
    }
2128
2129
    /**
2130
     * Fix a double (or multiple) encoded UTF8 string.
2131
     *
2132
     * @param string|string[] $str you can use a string or an array of strings
2133
     *
2134
     * @psalm-pure
2135
     *
2136
     * @return string|string[]
2137
     *                         Will return the fixed input-"array" or
2138
     *                         the fixed input-"string"
2139
     *
2140
     * @psalm-suppress InvalidReturnType
2141
     */
2142 2
    public static function fix_utf8($str)
2143
    {
2144 2
        if (\is_array($str)) {
2145 2
            foreach ($str as $k => &$v) {
2146 2
                $v = self::fix_utf8($v);
2147
            }
2148 2
            unset($v);
2149
2150
            /**
2151
             * @psalm-suppress InvalidReturnStatement
2152
             */
2153 2
            return $str;
2154
        }
2155
2156 2
        $str = (string) $str;
2157 2
        $last = '';
2158 2
        while ($last !== $str) {
2159 2
            $last = $str;
2160
            /**
2161
             * @psalm-suppress PossiblyInvalidArgument
2162
             */
2163 2
            $str = self::to_utf8(
2164 2
                self::utf8_decode($str, true)
2165
            );
2166
        }
2167
2168
        /**
2169
         * @psalm-suppress InvalidReturnStatement
2170
         */
2171 2
        return $str;
2172
    }
2173
2174
    /**
2175
     * Get character of a specific character.
2176
     *
2177
     * @param string $char
2178
     *
2179
     * @psalm-pure
2180
     *
2181
     * @return string
2182
     *                <p>'RTL' or 'LTR'.</p>
2183
     */
2184 2
    public static function getCharDirection(string $char): string
2185
    {
2186 2
        if (self::$SUPPORT['intlChar'] === true) {
2187
            /** @noinspection PhpComposerExtensionStubsInspection */
2188 2
            $tmp_return = \IntlChar::charDirection($char);
2189
2190
            // from "IntlChar"-Class
2191
            $char_direction = [
2192 2
                'RTL' => [1, 13, 14, 15, 21],
2193
                'LTR' => [0, 11, 12, 20],
2194
            ];
2195
2196 2
            if (\in_array($tmp_return, $char_direction['LTR'], true)) {
2197
                return 'LTR';
2198
            }
2199
2200 2
            if (\in_array($tmp_return, $char_direction['RTL'], true)) {
2201 2
                return 'RTL';
2202
            }
2203
        }
2204
2205 2
        $c = static::chr_to_decimal($char);
2206
2207 2
        if (!($c >= 0x5be && $c <= 0x10b7f)) {
2208 2
            return 'LTR';
2209
        }
2210
2211 2
        if ($c <= 0x85e) {
2212 2
            if ($c === 0x5be ||
2213 2
                $c === 0x5c0 ||
2214 2
                $c === 0x5c3 ||
2215 2
                $c === 0x5c6 ||
2216 2
                ($c >= 0x5d0 && $c <= 0x5ea) ||
2217 2
                ($c >= 0x5f0 && $c <= 0x5f4) ||
2218 2
                $c === 0x608 ||
2219 2
                $c === 0x60b ||
2220 2
                $c === 0x60d ||
2221 2
                $c === 0x61b ||
2222 2
                ($c >= 0x61e && $c <= 0x64a) ||
2223
                ($c >= 0x66d && $c <= 0x66f) ||
2224
                ($c >= 0x671 && $c <= 0x6d5) ||
2225
                ($c >= 0x6e5 && $c <= 0x6e6) ||
2226
                ($c >= 0x6ee && $c <= 0x6ef) ||
2227
                ($c >= 0x6fa && $c <= 0x70d) ||
2228
                $c === 0x710 ||
2229
                ($c >= 0x712 && $c <= 0x72f) ||
2230
                ($c >= 0x74d && $c <= 0x7a5) ||
2231
                $c === 0x7b1 ||
2232
                ($c >= 0x7c0 && $c <= 0x7ea) ||
2233
                ($c >= 0x7f4 && $c <= 0x7f5) ||
2234
                $c === 0x7fa ||
2235
                ($c >= 0x800 && $c <= 0x815) ||
2236
                $c === 0x81a ||
2237
                $c === 0x824 ||
2238
                $c === 0x828 ||
2239
                ($c >= 0x830 && $c <= 0x83e) ||
2240
                ($c >= 0x840 && $c <= 0x858) ||
2241 2
                $c === 0x85e
2242
            ) {
2243 2
                return 'RTL';
2244
            }
2245 2
        } elseif ($c === 0x200f) {
2246
            return 'RTL';
2247 2
        } elseif ($c >= 0xfb1d) {
2248 2
            if ($c === 0xfb1d ||
2249 2
                ($c >= 0xfb1f && $c <= 0xfb28) ||
2250 2
                ($c >= 0xfb2a && $c <= 0xfb36) ||
2251 2
                ($c >= 0xfb38 && $c <= 0xfb3c) ||
2252 2
                $c === 0xfb3e ||
2253 2
                ($c >= 0xfb40 && $c <= 0xfb41) ||
2254 2
                ($c >= 0xfb43 && $c <= 0xfb44) ||
2255 2
                ($c >= 0xfb46 && $c <= 0xfbc1) ||
2256 2
                ($c >= 0xfbd3 && $c <= 0xfd3d) ||
2257 2
                ($c >= 0xfd50 && $c <= 0xfd8f) ||
2258 2
                ($c >= 0xfd92 && $c <= 0xfdc7) ||
2259 2
                ($c >= 0xfdf0 && $c <= 0xfdfc) ||
2260 2
                ($c >= 0xfe70 && $c <= 0xfe74) ||
2261 2
                ($c >= 0xfe76 && $c <= 0xfefc) ||
2262 2
                ($c >= 0x10800 && $c <= 0x10805) ||
2263 2
                $c === 0x10808 ||
2264 2
                ($c >= 0x1080a && $c <= 0x10835) ||
2265 2
                ($c >= 0x10837 && $c <= 0x10838) ||
2266 2
                $c === 0x1083c ||
2267 2
                ($c >= 0x1083f && $c <= 0x10855) ||
2268 2
                ($c >= 0x10857 && $c <= 0x1085f) ||
2269 2
                ($c >= 0x10900 && $c <= 0x1091b) ||
2270 2
                ($c >= 0x10920 && $c <= 0x10939) ||
2271 2
                $c === 0x1093f ||
2272 2
                $c === 0x10a00 ||
2273 2
                ($c >= 0x10a10 && $c <= 0x10a13) ||
2274 2
                ($c >= 0x10a15 && $c <= 0x10a17) ||
2275 2
                ($c >= 0x10a19 && $c <= 0x10a33) ||
2276 2
                ($c >= 0x10a40 && $c <= 0x10a47) ||
2277 2
                ($c >= 0x10a50 && $c <= 0x10a58) ||
2278 2
                ($c >= 0x10a60 && $c <= 0x10a7f) ||
2279 2
                ($c >= 0x10b00 && $c <= 0x10b35) ||
2280 2
                ($c >= 0x10b40 && $c <= 0x10b55) ||
2281 2
                ($c >= 0x10b58 && $c <= 0x10b72) ||
2282 2
                ($c >= 0x10b78 && $c <= 0x10b7f)
2283
            ) {
2284 2
                return 'RTL';
2285
            }
2286
        }
2287
2288 2
        return 'LTR';
2289
    }
2290
2291
    /**
2292
     * Check for php-support.
2293
     *
2294
     * @param string|null $key
2295
     *
2296
     * @psalm-pure
2297
     *
2298
     * @return mixed
2299
     *               Return the full support-"array", if $key === null<br>
2300
     *               return bool-value, if $key is used and available<br>
2301
     *               otherwise return <strong>null</strong>
2302
     */
2303 27
    public static function getSupportInfo(string $key = null)
2304
    {
2305 27
        if ($key === null) {
2306 4
            return self::$SUPPORT;
2307
        }
2308
2309 25
        if (self::$INTL_TRANSLITERATOR_LIST === null) {
2310 1
            self::$INTL_TRANSLITERATOR_LIST = self::getData('transliterator_list');
2311
        }
2312
        // compatibility fix for old versions
2313 25
        self::$SUPPORT['intl__transliterator_list_ids'] = self::$INTL_TRANSLITERATOR_LIST;
2314
2315 25
        return self::$SUPPORT[$key] ?? null;
2316
    }
2317
2318
    /**
2319
     * Warning: this method only works for some file-types (png, jpg)
2320
     *          if you need more supported types, please use e.g. "finfo"
2321
     *
2322
     * @param string $str
2323
     * @param array  $fallback <p>with this keys: 'ext', 'mime', 'type'
2324
     *
2325
     * @psalm-pure
2326
     *
2327
     * @return array<string, string|null>
2328
     *                       <p>with this keys: 'ext', 'mime', 'type'</p>
2329
     *
2330
     * @phpstan-param array{ext: null|string, mime: null|string, type: null|string} $fallback
2331
     */
2332 39
    public static function get_file_type(
2333
        string $str,
2334
        array $fallback = [
2335
            'ext'  => null,
2336
            'mime' => 'application/octet-stream',
2337
            'type' => null,
2338
        ]
2339
    ): array {
2340 39
        if ($str === '') {
2341
            return $fallback;
2342
        }
2343
2344
        /** @var false|string $str_info - needed for PhpStan (stubs error) */
2345 39
        $str_info = \substr($str, 0, 2);
2346 39
        if ($str_info === false || \strlen($str_info) !== 2) {
2347 11
            return $fallback;
2348
        }
2349
2350
        // DEBUG
2351
        //var_dump($str_info);
2352
2353 35
        $str_info = \unpack('C2chars', $str_info);
2354
2355
        /** @noinspection PhpSillyAssignmentInspection */
2356
        /** @var array|false $str_info - needed for PhpStan (stubs error) */
2357 35
        $str_info = $str_info;
2358
2359 35
        if ($str_info === false) {
2360
            return $fallback;
2361
        }
2362
        /** @noinspection OffsetOperationsInspection */
2363 35
        $type_code = (int) ($str_info['chars1'] . $str_info['chars2']);
2364
2365
        // DEBUG
2366
        //var_dump($type_code);
2367
2368
        //
2369
        // info: https://en.wikipedia.org/wiki/Magic_number_%28programming%29#Format_indicator
2370
        //
2371
        switch ($type_code) {
2372
            // WARNING: do not add too simple comparisons, because of false-positive results:
2373
            //
2374
            // 3780 => 'pdf', 7790 => 'exe', 7784 => 'midi', 8075 => 'zip',
2375
            // 8297 => 'rar', 7173 => 'gif', 7373 => 'tiff' 6677 => 'bmp', ...
2376
            //
2377 35
            case 255216:
2378
                $ext = 'jpg';
2379
                $mime = 'image/jpeg';
2380
                $type = 'binary';
2381
2382
                break;
2383 35
            case 13780:
2384 7
                $ext = 'png';
2385 7
                $mime = 'image/png';
2386 7
                $type = 'binary';
2387
2388 7
                break;
2389
            default:
2390 34
                return $fallback;
2391
        }
2392
2393
        return [
2394 7
            'ext'  => $ext,
2395 7
            'mime' => $mime,
2396 7
            'type' => $type,
2397
        ];
2398
    }
2399
2400
    /**
2401
     * @param int    $length         <p>Length of the random string.</p>
2402
     * @param string $possible_chars [optional] <p>Characters string for the random selection.</p>
2403
     * @param string $encoding       [optional] <p>Set the charset for e.g. "mb_" function</p>
2404
     *
2405
     * @return string
2406
     */
2407 1
    public static function get_random_string(
2408
        int $length,
2409
        string $possible_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
2410
        string $encoding = 'UTF-8'
2411
    ): string {
2412
        // init
2413 1
        $i = 0;
2414 1
        $str = '';
2415
2416
        //
2417
        // add random chars
2418
        //
2419
2420 1
        if ($encoding === 'UTF-8') {
2421 1
            $max_length = (int) \mb_strlen($possible_chars);
2422 1
            if ($max_length === 0) {
2423 1
                return '';
2424
            }
2425
2426 1
            while ($i < $length) {
2427
                try {
2428 1
                    $rand_int = \random_int(0, $max_length - 1);
2429
                } catch (\Exception $e) {
2430
                    /** @noinspection RandomApiMigrationInspection */
2431
                    $rand_int = \mt_rand(0, $max_length - 1);
2432
                }
2433 1
                $char = \mb_substr($possible_chars, $rand_int, 1);
2434 1
                if ($char !== false) {
2435 1
                    $str .= $char;
2436 1
                    ++$i;
2437
                }
2438
            }
2439
        } else {
2440
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
2441
2442
            $max_length = (int) self::strlen($possible_chars, $encoding);
2443
            if ($max_length === 0) {
2444
                return '';
2445
            }
2446
2447
            while ($i < $length) {
2448
                try {
2449
                    $rand_int = \random_int(0, $max_length - 1);
2450
                } catch (\Exception $e) {
2451
                    /** @noinspection RandomApiMigrationInspection */
2452
                    $rand_int = \mt_rand(0, $max_length - 1);
2453
                }
2454
                $char = self::substr($possible_chars, $rand_int, 1, $encoding);
2455
                if ($char !== false) {
2456
                    $str .= $char;
2457
                    ++$i;
2458
                }
2459
            }
2460
        }
2461
2462 1
        return $str;
2463
    }
2464
2465
    /**
2466
     * @param int|string $extra_entropy [optional] <p>Extra entropy via a string or int value.</p>
2467
     * @param bool       $use_md5       [optional] <p>Return the unique identifier as md5-hash? Default: true</p>
2468
     *
2469
     * @return string
2470
     */
2471 1
    public static function get_unique_string($extra_entropy = '', bool $use_md5 = true): string
2472
    {
2473
        try {
2474 1
            $rand_int = \random_int(0, \mt_getrandmax());
2475
        } catch (\Exception $e) {
2476
            /** @noinspection RandomApiMigrationInspection */
2477
            $rand_int = \mt_rand(0, \mt_getrandmax());
2478
        }
2479
2480
        $unique_helper = $rand_int .
2481 1
                         \session_id() .
2482 1
                         ($_SERVER['REMOTE_ADDR'] ?? '') .
2483 1
                         ($_SERVER['SERVER_ADDR'] ?? '') .
2484 1
                         $extra_entropy;
2485
2486 1
        $unique_string = \uniqid($unique_helper, true);
2487
2488 1
        if ($use_md5) {
2489 1
            $unique_string = \md5($unique_string . $unique_helper);
2490
        }
2491
2492 1
        return $unique_string;
2493
    }
2494
2495
    /**
2496
     * alias for "UTF8::string_has_bom()"
2497
     *
2498
     * @param string $str
2499
     *
2500
     * @psalm-pure
2501
     *
2502
     * @return bool
2503
     *
2504
     * @see        UTF8::string_has_bom()
2505
     * @deprecated <p>please use "UTF8::string_has_bom()"</p>
2506
     */
2507 2
    public static function hasBom(string $str): bool
2508
    {
2509 2
        return self::string_has_bom($str);
2510
    }
2511
2512
    /**
2513
     * Returns true if the string contains a lower case char, false otherwise.
2514
     *
2515
     * @param string $str <p>The input string.</p>
2516
     *
2517
     * @psalm-pure
2518
     *
2519
     * @return bool
2520
     *              <p>Whether or not the string contains a lower case character.</p>
2521
     */
2522 47
    public static function has_lowercase(string $str): bool
2523
    {
2524 47
        if (self::$SUPPORT['mbstring'] === true) {
2525
            /** @noinspection PhpComposerExtensionStubsInspection */
2526 47
            return \mb_ereg_match('.*[[:lower:]]', $str);
2527
        }
2528
2529
        return self::str_matches_pattern($str, '.*[[:lower:]]');
2530
    }
2531
2532
    /**
2533
     * Returns true if the string contains whitespace, false otherwise.
2534
     *
2535
     * @param string $str <p>The input string.</p>
2536
     *
2537
     * @psalm-pure
2538
     *
2539
     * @return bool
2540
     *              <p>Whether or not the string contains whitespace.</p>
2541
     */
2542 11
    public static function has_whitespace(string $str): bool
2543
    {
2544 11
        if (self::$SUPPORT['mbstring'] === true) {
2545
            /** @noinspection PhpComposerExtensionStubsInspection */
2546 11
            return \mb_ereg_match('.*[[:space:]]', $str);
2547
        }
2548
2549
        return self::str_matches_pattern($str, '.*[[:space:]]');
2550
    }
2551
2552
    /**
2553
     * Returns true if the string contains an upper case char, false otherwise.
2554
     *
2555
     * @param string $str <p>The input string.</p>
2556
     *
2557
     * @psalm-pure
2558
     *
2559
     * @return bool whether or not the string contains an upper case character
2560
     */
2561 12
    public static function has_uppercase(string $str): bool
2562
    {
2563 12
        if (self::$SUPPORT['mbstring'] === true) {
2564
            /** @noinspection PhpComposerExtensionStubsInspection */
2565 12
            return \mb_ereg_match('.*[[:upper:]]', $str);
2566
        }
2567
2568
        return self::str_matches_pattern($str, '.*[[:upper:]]');
2569
    }
2570
2571
    /**
2572
     * Converts a hexadecimal value into a UTF-8 character.
2573
     *
2574
     * @param string $hexdec <p>The hexadecimal value.</p>
2575
     *
2576
     * @psalm-pure
2577
     *
2578
     * @return false|string one single UTF-8 character
2579
     */
2580 4
    public static function hex_to_chr(string $hexdec)
2581
    {
2582 4
        return self::decimal_to_chr(\hexdec($hexdec));
2583
    }
2584
2585
    /**
2586
     * Converts hexadecimal U+xxxx code point representation to integer.
2587
     *
2588
     * INFO: opposite to UTF8::int_to_hex()
2589
     *
2590
     * @param string $hexdec <p>The hexadecimal code point representation.</p>
2591
     *
2592
     * @psalm-pure
2593
     *
2594
     * @return false|int
2595
     *                   <p>The code point, or false on failure.</p>
2596
     */
2597 2
    public static function hex_to_int($hexdec)
2598
    {
2599
        // init
2600 2
        $hexdec = (string) $hexdec;
2601
2602 2
        if ($hexdec === '') {
2603 2
            return false;
2604
        }
2605
2606 2
        if (\preg_match('/^(?:\\\u|U\+|)([a-zA-Z0-9]{4,6})$/', $hexdec, $match)) {
2607 2
            return \intval($match[1], 16);
2608
        }
2609
2610 2
        return false;
2611
    }
2612
2613
    /**
2614
     * alias for "UTF8::html_entity_decode()"
2615
     *
2616
     * @param string $str
2617
     * @param int    $flags
2618
     * @param string $encoding
2619
     *
2620
     * @psalm-pure
2621
     *
2622
     * @return string
2623
     *
2624
     * @see        UTF8::html_entity_decode()
2625
     * @deprecated <p>please use "UTF8::html_entity_decode()"</p>
2626
     */
2627 2
    public static function html_decode(
2628
        string $str,
2629
        int $flags = null,
2630
        string $encoding = 'UTF-8'
2631
    ): string {
2632 2
        return self::html_entity_decode($str, $flags, $encoding);
2633
    }
2634
2635
    /**
2636
     * Converts a UTF-8 string to a series of HTML numbered entities.
2637
     *
2638
     * INFO: opposite to UTF8::html_decode()
2639
     *
2640
     * @param string $str              <p>The Unicode string to be encoded as numbered entities.</p>
2641
     * @param bool   $keep_ascii_chars [optional] <p>Keep ASCII chars.</p>
2642
     * @param string $encoding         [optional] <p>Set the charset for e.g. "mb_" function</p>
2643
     *
2644
     * @psalm-pure
2645
     *
2646
     * @return string HTML numbered entities
2647
     */
2648 14
    public static function html_encode(
2649
        string $str,
2650
        bool $keep_ascii_chars = false,
2651
        string $encoding = 'UTF-8'
2652
    ): string {
2653 14
        if ($str === '') {
2654 4
            return '';
2655
        }
2656
2657 14
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
2658 4
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
2659
        }
2660
2661
        // INFO: http://stackoverflow.com/questions/35854535/better-explanation-of-convmap-in-mb-encode-numericentity
2662 14
        if (self::$SUPPORT['mbstring'] === true) {
2663 14
            $start_code = 0x00;
2664 14
            if ($keep_ascii_chars) {
2665 13
                $start_code = 0x80;
2666
            }
2667
2668 14
            if ($encoding === 'UTF-8') {
2669
                /** @var false|string|null $return - needed for PhpStan (stubs error) */
2670 14
                $return = \mb_encode_numericentity(
2671 14
                    $str,
2672 14
                    [$start_code, 0xfffff, 0, 0xfffff, 0]
2673
                );
2674 14
                if ($return !== null && $return !== false) {
2675 14
                    return $return;
2676
                }
2677
            }
2678
2679
            /** @var false|string|null $return - needed for PhpStan (stubs error) */
2680 4
            $return = \mb_encode_numericentity(
2681 4
                $str,
2682 4
                [$start_code, 0xfffff, 0, 0xfffff, 0],
2683 4
                $encoding
2684
            );
2685 4
            if ($return !== null && $return !== false) {
2686 4
                return $return;
2687
            }
2688
        }
2689
2690
        //
2691
        // fallback via vanilla php
2692
        //
2693
2694
        return \implode(
2695
            '',
2696
            \array_map(
2697
                static function (string $chr) use ($keep_ascii_chars, $encoding): string {
2698
                    return self::single_chr_html_encode($chr, $keep_ascii_chars, $encoding);
2699
                },
2700
                self::str_split($str)
2701
            )
2702
        );
2703
    }
2704
2705
    /**
2706
     * UTF-8 version of html_entity_decode()
2707
     *
2708
     * The reason we are not using html_entity_decode() by itself is because
2709
     * while it is not technically correct to leave out the semicolon
2710
     * at the end of an entity most browsers will still interpret the entity
2711
     * correctly. html_entity_decode() does not convert entities without
2712
     * semicolons, so we are left with our own little solution here. Bummer.
2713
     *
2714
     * Convert all HTML entities to their applicable characters
2715
     *
2716
     * INFO: opposite to UTF8::html_encode()
2717
     *
2718
     * @see http://php.net/manual/en/function.html-entity-decode.php
2719
     *
2720
     * @param string $str      <p>
2721
     *                         The input string.
2722
     *                         </p>
2723
     * @param int    $flags    [optional] <p>
2724
     *                         A bitmask of one or more of the following flags, which specify how to handle quotes
2725
     *                         and which document type to use. The default is ENT_COMPAT | ENT_HTML401.
2726
     *                         <table>
2727
     *                         Available <i>flags</i> constants
2728
     *                         <tr valign="top">
2729
     *                         <td>Constant Name</td>
2730
     *                         <td>Description</td>
2731
     *                         </tr>
2732
     *                         <tr valign="top">
2733
     *                         <td><b>ENT_COMPAT</b></td>
2734
     *                         <td>Will convert double-quotes and leave single-quotes alone.</td>
2735
     *                         </tr>
2736
     *                         <tr valign="top">
2737
     *                         <td><b>ENT_QUOTES</b></td>
2738
     *                         <td>Will convert both double and single quotes.</td>
2739
     *                         </tr>
2740
     *                         <tr valign="top">
2741
     *                         <td><b>ENT_NOQUOTES</b></td>
2742
     *                         <td>Will leave both double and single quotes unconverted.</td>
2743
     *                         </tr>
2744
     *                         <tr valign="top">
2745
     *                         <td><b>ENT_HTML401</b></td>
2746
     *                         <td>
2747
     *                         Handle code as HTML 4.01.
2748
     *                         </td>
2749
     *                         </tr>
2750
     *                         <tr valign="top">
2751
     *                         <td><b>ENT_XML1</b></td>
2752
     *                         <td>
2753
     *                         Handle code as XML 1.
2754
     *                         </td>
2755
     *                         </tr>
2756
     *                         <tr valign="top">
2757
     *                         <td><b>ENT_XHTML</b></td>
2758
     *                         <td>
2759
     *                         Handle code as XHTML.
2760
     *                         </td>
2761
     *                         </tr>
2762
     *                         <tr valign="top">
2763
     *                         <td><b>ENT_HTML5</b></td>
2764
     *                         <td>
2765
     *                         Handle code as HTML 5.
2766
     *                         </td>
2767
     *                         </tr>
2768
     *                         </table>
2769
     *                         </p>
2770
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
2771
     *
2772
     * @psalm-pure
2773
     *
2774
     * @return string the decoded string
2775
     */
2776 51
    public static function html_entity_decode(
2777
        string $str,
2778
        int $flags = null,
2779
        string $encoding = 'UTF-8'
2780
    ): string {
2781
        if (
2782 51
            !isset($str[3]) // examples: &; || &x;
2783
            ||
2784 51
            \strpos($str, '&') === false // no "&"
2785
        ) {
2786 24
            return $str;
2787
        }
2788
2789 49
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
2790 9
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
2791
        }
2792
2793 49
        if ($flags === null) {
2794 11
            $flags = \ENT_QUOTES | \ENT_HTML5;
2795
        }
2796
2797
        if (
2798 49
            $encoding !== 'UTF-8'
2799
            &&
2800 49
            $encoding !== 'ISO-8859-1'
2801
            &&
2802 49
            $encoding !== 'WINDOWS-1252'
2803
            &&
2804 49
            self::$SUPPORT['mbstring'] === false
2805
        ) {
2806
            /**
2807
             * @psalm-suppress ImpureFunctionCall - is is only a warning
2808
             */
2809
            \trigger_error('UTF8::html_entity_decode() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
2810
        }
2811
2812
        do {
2813 49
            $str_compare = $str;
2814
2815 49
            if (\strpos($str, '&') !== false) {
2816 49
                if (\strpos($str, '&#') !== false) {
2817
                    // decode also numeric & UTF16 two byte entities
2818 41
                    $str = (string) \preg_replace(
2819 41
                        '/(&#(?:x0*[0-9a-fA-F]{2,6}(?![0-9a-fA-F;])|(?:0*\d{2,6}(?![0-9;]))))/S',
2820 41
                        '$1;',
2821 41
                        $str
2822
                    );
2823
                }
2824
2825 49
                $str = \html_entity_decode(
2826 49
                    $str,
2827 49
                    $flags,
2828 49
                    $encoding
2829
                );
2830
            }
2831 49
        } while ($str_compare !== $str);
2832
2833 49
        return $str;
2834
    }
2835
2836
    /**
2837
     * Create a escape html version of the string via "UTF8::htmlspecialchars()".
2838
     *
2839
     * @param string $str
2840
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
2841
     *
2842
     * @psalm-pure
2843
     *
2844
     * @return string
2845
     */
2846 6
    public static function html_escape(string $str, string $encoding = 'UTF-8'): string
2847
    {
2848 6
        return self::htmlspecialchars(
2849 6
            $str,
2850 6
            \ENT_QUOTES | \ENT_SUBSTITUTE,
2851 6
            $encoding
2852
        );
2853
    }
2854
2855
    /**
2856
     * Remove empty html-tag.
2857
     *
2858
     * e.g.: <tag></tag>
2859
     *
2860
     * @param string $str
2861
     *
2862
     * @psalm-pure
2863
     *
2864
     * @return string
2865
     */
2866 1
    public static function html_stripe_empty_tags(string $str): string
2867
    {
2868 1
        return (string) \preg_replace(
2869 1
            '/<[^\\/>]*?>\\s*?<\\/[^>]*?>/u',
2870 1
            '',
2871 1
            $str
2872
        );
2873
    }
2874
2875
    /**
2876
     * Convert all applicable characters to HTML entities: UTF-8 version of htmlentities()
2877
     *
2878
     * @see http://php.net/manual/en/function.htmlentities.php
2879
     *
2880
     * @param string $str           <p>
2881
     *                              The input string.
2882
     *                              </p>
2883
     * @param int    $flags         [optional] <p>
2884
     *                              A bitmask of one or more of the following flags, which specify how to handle
2885
     *                              quotes, invalid code unit sequences and the used document type. The default is
2886
     *                              ENT_COMPAT | ENT_HTML401.
2887
     *                              <table>
2888
     *                              Available <i>flags</i> constants
2889
     *                              <tr valign="top">
2890
     *                              <td>Constant Name</td>
2891
     *                              <td>Description</td>
2892
     *                              </tr>
2893
     *                              <tr valign="top">
2894
     *                              <td><b>ENT_COMPAT</b></td>
2895
     *                              <td>Will convert double-quotes and leave single-quotes alone.</td>
2896
     *                              </tr>
2897
     *                              <tr valign="top">
2898
     *                              <td><b>ENT_QUOTES</b></td>
2899
     *                              <td>Will convert both double and single quotes.</td>
2900
     *                              </tr>
2901
     *                              <tr valign="top">
2902
     *                              <td><b>ENT_NOQUOTES</b></td>
2903
     *                              <td>Will leave both double and single quotes unconverted.</td>
2904
     *                              </tr>
2905
     *                              <tr valign="top">
2906
     *                              <td><b>ENT_IGNORE</b></td>
2907
     *                              <td>
2908
     *                              Silently discard invalid code unit sequences instead of returning
2909
     *                              an empty string. Using this flag is discouraged as it
2910
     *                              may have security implications.
2911
     *                              </td>
2912
     *                              </tr>
2913
     *                              <tr valign="top">
2914
     *                              <td><b>ENT_SUBSTITUTE</b></td>
2915
     *                              <td>
2916
     *                              Replace invalid code unit sequences with a Unicode Replacement Character
2917
     *                              U+FFFD (UTF-8) or &#38;#38;#FFFD; (otherwise) instead of returning an empty
2918
     *                              string.
2919
     *                              </td>
2920
     *                              </tr>
2921
     *                              <tr valign="top">
2922
     *                              <td><b>ENT_DISALLOWED</b></td>
2923
     *                              <td>
2924
     *                              Replace invalid code points for the given document type with a
2925
     *                              Unicode Replacement Character U+FFFD (UTF-8) or &#38;#38;#FFFD;
2926
     *                              (otherwise) instead of leaving them as is. This may be useful, for
2927
     *                              instance, to ensure the well-formedness of XML documents with
2928
     *                              embedded external content.
2929
     *                              </td>
2930
     *                              </tr>
2931
     *                              <tr valign="top">
2932
     *                              <td><b>ENT_HTML401</b></td>
2933
     *                              <td>
2934
     *                              Handle code as HTML 4.01.
2935
     *                              </td>
2936
     *                              </tr>
2937
     *                              <tr valign="top">
2938
     *                              <td><b>ENT_XML1</b></td>
2939
     *                              <td>
2940
     *                              Handle code as XML 1.
2941
     *                              </td>
2942
     *                              </tr>
2943
     *                              <tr valign="top">
2944
     *                              <td><b>ENT_XHTML</b></td>
2945
     *                              <td>
2946
     *                              Handle code as XHTML.
2947
     *                              </td>
2948
     *                              </tr>
2949
     *                              <tr valign="top">
2950
     *                              <td><b>ENT_HTML5</b></td>
2951
     *                              <td>
2952
     *                              Handle code as HTML 5.
2953
     *                              </td>
2954
     *                              </tr>
2955
     *                              </table>
2956
     *                              </p>
2957
     * @param string $encoding      [optional] <p>
2958
     *                              Like <b>htmlspecialchars</b>,
2959
     *                              <b>htmlentities</b> takes an optional third argument
2960
     *                              <i>encoding</i> which defines encoding used in
2961
     *                              conversion.
2962
     *                              Although this argument is technically optional, you are highly
2963
     *                              encouraged to specify the correct value for your code.
2964
     *                              </p>
2965
     * @param bool   $double_encode [optional] <p>
2966
     *                              When <i>double_encode</i> is turned off PHP will not
2967
     *                              encode existing html entities. The default is to convert everything.
2968
     *                              </p>
2969
     *
2970
     * @psalm-pure
2971
     *
2972
     * @return string
2973
     *                <p>
2974
     *                The encoded string.
2975
     *                <br><br>
2976
     *                If the input <i>string</i> contains an invalid code unit
2977
     *                sequence within the given <i>encoding</i> an empty string
2978
     *                will be returned, unless either the <b>ENT_IGNORE</b> or
2979
     *                <b>ENT_SUBSTITUTE</b> flags are set.
2980
     *                </p>
2981
     */
2982 9
    public static function htmlentities(
2983
        string $str,
2984
        int $flags = \ENT_COMPAT,
2985
        string $encoding = 'UTF-8',
2986
        bool $double_encode = true
2987
    ): string {
2988 9
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
2989 7
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
2990
        }
2991
2992 9
        $str = \htmlentities(
2993 9
            $str,
2994 9
            $flags,
2995 9
            $encoding,
2996 9
            $double_encode
2997
        );
2998
2999
        /**
3000
         * PHP doesn't replace a backslash to its html entity since this is something
3001
         * that's mostly used to escape characters when inserting in a database. Since
3002
         * we're using a decent database layer, we don't need this shit and we're replacing
3003
         * the double backslashes by its' html entity equivalent.
3004
         *
3005
         * https://github.com/forkcms/library/blob/master/spoon/filter/filter.php#L303
3006
         */
3007 9
        $str = \str_replace('\\', '&#92;', $str);
3008
3009 9
        return self::html_encode($str, true, $encoding);
3010
    }
3011
3012
    /**
3013
     * Convert only special characters to HTML entities: UTF-8 version of htmlspecialchars()
3014
     *
3015
     * INFO: Take a look at "UTF8::htmlentities()"
3016
     *
3017
     * @see http://php.net/manual/en/function.htmlspecialchars.php
3018
     *
3019
     * @param string $str           <p>
3020
     *                              The string being converted.
3021
     *                              </p>
3022
     * @param int    $flags         [optional] <p>
3023
     *                              A bitmask of one or more of the following flags, which specify how to handle
3024
     *                              quotes, invalid code unit sequences and the used document type. The default is
3025
     *                              ENT_COMPAT | ENT_HTML401.
3026
     *                              <table>
3027
     *                              Available <i>flags</i> constants
3028
     *                              <tr valign="top">
3029
     *                              <td>Constant Name</td>
3030
     *                              <td>Description</td>
3031
     *                              </tr>
3032
     *                              <tr valign="top">
3033
     *                              <td><b>ENT_COMPAT</b></td>
3034
     *                              <td>Will convert double-quotes and leave single-quotes alone.</td>
3035
     *                              </tr>
3036
     *                              <tr valign="top">
3037
     *                              <td><b>ENT_QUOTES</b></td>
3038
     *                              <td>Will convert both double and single quotes.</td>
3039
     *                              </tr>
3040
     *                              <tr valign="top">
3041
     *                              <td><b>ENT_NOQUOTES</b></td>
3042
     *                              <td>Will leave both double and single quotes unconverted.</td>
3043
     *                              </tr>
3044
     *                              <tr valign="top">
3045
     *                              <td><b>ENT_IGNORE</b></td>
3046
     *                              <td>
3047
     *                              Silently discard invalid code unit sequences instead of returning
3048
     *                              an empty string. Using this flag is discouraged as it
3049
     *                              may have security implications.
3050
     *                              </td>
3051
     *                              </tr>
3052
     *                              <tr valign="top">
3053
     *                              <td><b>ENT_SUBSTITUTE</b></td>
3054
     *                              <td>
3055
     *                              Replace invalid code unit sequences with a Unicode Replacement Character
3056
     *                              U+FFFD (UTF-8) or &#38;#38;#FFFD; (otherwise) instead of returning an empty
3057
     *                              string.
3058
     *                              </td>
3059
     *                              </tr>
3060
     *                              <tr valign="top">
3061
     *                              <td><b>ENT_DISALLOWED</b></td>
3062
     *                              <td>
3063
     *                              Replace invalid code points for the given document type with a
3064
     *                              Unicode Replacement Character U+FFFD (UTF-8) or &#38;#38;#FFFD;
3065
     *                              (otherwise) instead of leaving them as is. This may be useful, for
3066
     *                              instance, to ensure the well-formedness of XML documents with
3067
     *                              embedded external content.
3068
     *                              </td>
3069
     *                              </tr>
3070
     *                              <tr valign="top">
3071
     *                              <td><b>ENT_HTML401</b></td>
3072
     *                              <td>
3073
     *                              Handle code as HTML 4.01.
3074
     *                              </td>
3075
     *                              </tr>
3076
     *                              <tr valign="top">
3077
     *                              <td><b>ENT_XML1</b></td>
3078
     *                              <td>
3079
     *                              Handle code as XML 1.
3080
     *                              </td>
3081
     *                              </tr>
3082
     *                              <tr valign="top">
3083
     *                              <td><b>ENT_XHTML</b></td>
3084
     *                              <td>
3085
     *                              Handle code as XHTML.
3086
     *                              </td>
3087
     *                              </tr>
3088
     *                              <tr valign="top">
3089
     *                              <td><b>ENT_HTML5</b></td>
3090
     *                              <td>
3091
     *                              Handle code as HTML 5.
3092
     *                              </td>
3093
     *                              </tr>
3094
     *                              </table>
3095
     *                              </p>
3096
     * @param string $encoding      [optional] <p>
3097
     *                              Defines encoding used in conversion.
3098
     *                              </p>
3099
     *                              <p>
3100
     *                              For the purposes of this function, the encodings
3101
     *                              ISO-8859-1, ISO-8859-15,
3102
     *                              UTF-8, cp866,
3103
     *                              cp1251, cp1252, and
3104
     *                              KOI8-R are effectively equivalent, provided the
3105
     *                              <i>string</i> itself is valid for the encoding, as
3106
     *                              the characters affected by <b>htmlspecialchars</b> occupy
3107
     *                              the same positions in all of these encodings.
3108
     *                              </p>
3109
     * @param bool   $double_encode [optional] <p>
3110
     *                              When <i>double_encode</i> is turned off PHP will not
3111
     *                              encode existing html entities, the default is to convert everything.
3112
     *                              </p>
3113
     *
3114
     * @psalm-pure
3115
     *
3116
     * @return string the converted string.
3117
     *                </p>
3118
     *                <p>
3119
     *                If the input <i>string</i> contains an invalid code unit
3120
     *                sequence within the given <i>encoding</i> an empty string
3121
     *                will be returned, unless either the <b>ENT_IGNORE</b> or
3122
     *                <b>ENT_SUBSTITUTE</b> flags are set
3123
     */
3124 8
    public static function htmlspecialchars(
3125
        string $str,
3126
        int $flags = \ENT_COMPAT,
3127
        string $encoding = 'UTF-8',
3128
        bool $double_encode = true
3129
    ): string {
3130 8
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
3131 8
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
3132
        }
3133
3134 8
        return \htmlspecialchars(
3135 8
            $str,
3136 8
            $flags,
3137 8
            $encoding,
3138 8
            $double_encode
3139
        );
3140
    }
3141
3142
    /**
3143
     * Checks whether iconv is available on the server.
3144
     *
3145
     * @psalm-pure
3146
     *
3147
     * @return bool
3148
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
3149
     */
3150
    public static function iconv_loaded(): bool
3151
    {
3152
        return \extension_loaded('iconv');
3153
    }
3154
3155
    /**
3156
     * alias for "UTF8::decimal_to_chr()"
3157
     *
3158
     * @param mixed $int
3159
     *
3160
     * @psalm-pure
3161
     *
3162
     * @return string
3163
     *
3164
     * @see        UTF8::decimal_to_chr()
3165
     * @deprecated <p>please use "UTF8::decimal_to_chr()"</p>
3166
     */
3167 4
    public static function int_to_chr($int): string
3168
    {
3169 4
        return self::decimal_to_chr($int);
3170
    }
3171
3172
    /**
3173
     * Converts Integer to hexadecimal U+xxxx code point representation.
3174
     *
3175
     * INFO: opposite to UTF8::hex_to_int()
3176
     *
3177
     * @param int    $int    <p>The integer to be converted to hexadecimal code point.</p>
3178
     * @param string $prefix [optional]
3179
     *
3180
     * @psalm-pure
3181
     *
3182
     * @return string the code point, or empty string on failure
3183
     */
3184 6
    public static function int_to_hex(int $int, string $prefix = 'U+'): string
3185
    {
3186 6
        $hex = \dechex($int);
3187
3188 6
        $hex = (\strlen($hex) < 4 ? \substr('0000' . $hex, -4) : $hex);
3189
3190 6
        return $prefix . $hex . '';
3191
    }
3192
3193
    /**
3194
     * Checks whether intl-char is available on the server.
3195
     *
3196
     * @psalm-pure
3197
     *
3198
     * @return bool
3199
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
3200
     */
3201
    public static function intlChar_loaded(): bool
3202
    {
3203
        return \class_exists('IntlChar');
3204
    }
3205
3206
    /**
3207
     * Checks whether intl is available on the server.
3208
     *
3209
     * @psalm-pure
3210
     *
3211
     * @return bool
3212
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
3213
     */
3214 5
    public static function intl_loaded(): bool
3215
    {
3216 5
        return \extension_loaded('intl');
3217
    }
3218
3219
    /**
3220
     * alias for "UTF8::is_ascii()"
3221
     *
3222
     * @param string $str
3223
     *
3224
     * @psalm-pure
3225
     *
3226
     * @return bool
3227
     *
3228
     * @see        UTF8::is_ascii()
3229
     * @deprecated <p>please use "UTF8::is_ascii()"</p>
3230
     */
3231 2
    public static function isAscii(string $str): bool
3232
    {
3233 2
        return ASCII::is_ascii($str);
3234
    }
3235
3236
    /**
3237
     * alias for "UTF8::is_base64()"
3238
     *
3239
     * @param string $str
3240
     *
3241
     * @psalm-pure
3242
     *
3243
     * @return bool
3244
     *
3245
     * @see        UTF8::is_base64()
3246
     * @deprecated <p>please use "UTF8::is_base64()"</p>
3247
     */
3248 2
    public static function isBase64($str): bool
3249
    {
3250 2
        return self::is_base64($str);
3251
    }
3252
3253
    /**
3254
     * alias for "UTF8::is_binary()"
3255
     *
3256
     * @param mixed $str
3257
     * @param bool  $strict
3258
     *
3259
     * @psalm-pure
3260
     *
3261
     * @return bool
3262
     *
3263
     * @see        UTF8::is_binary()
3264
     * @deprecated <p>please use "UTF8::is_binary()"</p>
3265
     */
3266 4
    public static function isBinary($str, bool $strict = false): bool
3267
    {
3268 4
        return self::is_binary($str, $strict);
3269
    }
3270
3271
    /**
3272
     * alias for "UTF8::is_bom()"
3273
     *
3274
     * @param string $utf8_chr
3275
     *
3276
     * @psalm-pure
3277
     *
3278
     * @return bool
3279
     *
3280
     * @see        UTF8::is_bom()
3281
     * @deprecated <p>please use "UTF8::is_bom()"</p>
3282
     */
3283 2
    public static function isBom(string $utf8_chr): bool
3284
    {
3285 2
        return self::is_bom($utf8_chr);
3286
    }
3287
3288
    /**
3289
     * alias for "UTF8::is_html()"
3290
     *
3291
     * @param string $str
3292
     *
3293
     * @psalm-pure
3294
     *
3295
     * @return bool
3296
     *
3297
     * @see        UTF8::is_html()
3298
     * @deprecated <p>please use "UTF8::is_html()"</p>
3299
     */
3300 2
    public static function isHtml(string $str): bool
3301
    {
3302 2
        return self::is_html($str);
3303
    }
3304
3305
    /**
3306
     * alias for "UTF8::is_json()"
3307
     *
3308
     * @param string $str
3309
     *
3310
     * @return bool
3311
     *
3312
     * @see        UTF8::is_json()
3313
     * @deprecated <p>please use "UTF8::is_json()"</p>
3314
     */
3315 1
    public static function isJson(string $str): bool
3316
    {
3317 1
        return self::is_json($str);
3318
    }
3319
3320
    /**
3321
     * alias for "UTF8::is_utf16()"
3322
     *
3323
     * @param mixed $str
3324
     *
3325
     * @psalm-pure
3326
     *
3327
     * @return false|int
3328
     *                   <strong>false</strong> if is't not UTF16,<br>
3329
     *                   <strong>1</strong> for UTF-16LE,<br>
3330
     *                   <strong>2</strong> for UTF-16BE
3331
     *
3332
     * @see        UTF8::is_utf16()
3333
     * @deprecated <p>please use "UTF8::is_utf16()"</p>
3334
     */
3335 2
    public static function isUtf16($str)
3336
    {
3337 2
        return self::is_utf16($str);
3338
    }
3339
3340
    /**
3341
     * alias for "UTF8::is_utf32()"
3342
     *
3343
     * @param mixed $str
3344
     *
3345
     * @psalm-pure
3346
     *
3347
     * @return false|int
3348
     *                   <strong>false</strong> if is't not UTF16,
3349
     *                   <strong>1</strong> for UTF-32LE,
3350
     *                   <strong>2</strong> for UTF-32BE
3351
     *
3352
     * @see        UTF8::is_utf32()
3353
     * @deprecated <p>please use "UTF8::is_utf32()"</p>
3354
     */
3355 2
    public static function isUtf32($str)
3356
    {
3357 2
        return self::is_utf32($str);
3358
    }
3359
3360
    /**
3361
     * alias for "UTF8::is_utf8()"
3362
     *
3363
     * @param string $str
3364
     * @param bool   $strict
3365
     *
3366
     * @psalm-pure
3367
     *
3368
     * @return bool
3369
     *
3370
     * @see        UTF8::is_utf8()
3371
     * @deprecated <p>please use "UTF8::is_utf8()"</p>
3372
     */
3373 17
    public static function isUtf8($str, bool $strict = false): bool
3374
    {
3375 17
        return self::is_utf8($str, $strict);
3376
    }
3377
3378
    /**
3379
     * Returns true if the string contains only alphabetic chars, false otherwise.
3380
     *
3381
     * @param string $str <p>The input string.</p>
3382
     *
3383
     * @psalm-pure
3384
     *
3385
     * @return bool
3386
     *              <p>Whether or not $str contains only alphabetic chars.</p>
3387
     */
3388 10
    public static function is_alpha(string $str): bool
3389
    {
3390 10
        if (self::$SUPPORT['mbstring'] === true) {
3391
            /** @noinspection PhpComposerExtensionStubsInspection */
3392 10
            return \mb_ereg_match('^[[:alpha:]]*$', $str);
3393
        }
3394
3395
        return self::str_matches_pattern($str, '^[[:alpha:]]*$');
3396
    }
3397
3398
    /**
3399
     * Returns true if the string contains only alphabetic and numeric chars, false otherwise.
3400
     *
3401
     * @param string $str <p>The input string.</p>
3402
     *
3403
     * @psalm-pure
3404
     *
3405
     * @return bool
3406
     *              <p>Whether or not $str contains only alphanumeric chars.</p>
3407
     */
3408 13
    public static function is_alphanumeric(string $str): bool
3409
    {
3410 13
        if (self::$SUPPORT['mbstring'] === true) {
3411
            /** @noinspection PhpComposerExtensionStubsInspection */
3412 13
            return \mb_ereg_match('^[[:alnum:]]*$', $str);
3413
        }
3414
3415
        return self::str_matches_pattern($str, '^[[:alnum:]]*$');
3416
    }
3417
3418
    /**
3419
     * Returns true if the string contains only punctuation chars, false otherwise.
3420
     *
3421
     * @param string $str <p>The input string.</p>
3422
     *
3423
     * @psalm-pure
3424
     *
3425
     * @return bool
3426
     *              <p>Whether or not $str contains only punctuation chars.</p>
3427
     */
3428 10
    public static function is_punctuation(string $str): bool
3429
    {
3430 10
        return self::str_matches_pattern($str, '^[[:punct:]]*$');
3431
    }
3432
3433
    /**
3434
     * Returns true if the string contains only printable (non-invisible) chars, false otherwise.
3435
     *
3436
     * @param string $str <p>The input string.</p>
3437
     *
3438
     * @psalm-pure
3439
     *
3440
     * @return bool
3441
     *              <p>Whether or not $str contains only printable (non-invisible) chars.</p>
3442
     */
3443 1
    public static function is_printable(string $str): bool
3444
    {
3445 1
        return self::remove_invisible_characters($str) === $str;
3446
    }
3447
3448
    /**
3449
     * Checks if a string is 7 bit ASCII.
3450
     *
3451
     * @param string $str <p>The string to check.</p>
3452
     *
3453
     * @psalm-pure
3454
     *
3455
     * @return bool
3456
     *              <p>
3457
     *              <strong>true</strong> if it is ASCII<br>
3458
     *              <strong>false</strong> otherwise
3459
     *              </p>
3460
     */
3461 8
    public static function is_ascii(string $str): bool
3462
    {
3463 8
        return ASCII::is_ascii($str);
3464
    }
3465
3466
    /**
3467
     * Returns true if the string is base64 encoded, false otherwise.
3468
     *
3469
     * @param mixed|string $str                   <p>The input string.</p>
3470
     * @param bool         $empty_string_is_valid [optional] <p>Is an empty string valid base64 or not?</p>
3471
     *
3472
     * @psalm-pure
3473
     *
3474
     * @return bool
3475
     *              <p>Whether or not $str is base64 encoded.</p>
3476
     */
3477 16
    public static function is_base64($str, bool $empty_string_is_valid = false): bool
3478
    {
3479
        if (
3480 16
            !$empty_string_is_valid
3481
            &&
3482 16
            $str === ''
3483
        ) {
3484 3
            return false;
3485
        }
3486
3487
        /**
3488
         * @psalm-suppress RedundantConditionGivenDocblockType
3489
         */
3490 15
        if (!\is_string($str)) {
3491 2
            return false;
3492
        }
3493
3494 15
        $base64String = \base64_decode($str, true);
3495
3496 15
        return $base64String !== false && \base64_encode($base64String) === $str;
3497
    }
3498
3499
    /**
3500
     * Check if the input is binary... (is look like a hack).
3501
     *
3502
     * @param mixed $input
3503
     * @param bool  $strict
3504
     *
3505
     * @psalm-pure
3506
     *
3507
     * @return bool
3508
     */
3509 39
    public static function is_binary($input, bool $strict = false): bool
3510
    {
3511 39
        $input = (string) $input;
3512 39
        if ($input === '') {
3513 10
            return false;
3514
        }
3515
3516 39
        if (\preg_match('~^[01]+$~', $input)) {
3517 13
            return true;
3518
        }
3519
3520 39
        $ext = self::get_file_type($input);
3521 39
        if ($ext['type'] === 'binary') {
3522 7
            return true;
3523
        }
3524
3525 38
        $test_length = \strlen($input);
3526 38
        $test_null_counting = \substr_count($input, "\x0", 0, $test_length);
3527 38
        if (($test_null_counting / $test_length) > 0.25) {
3528 15
            return true;
3529
        }
3530
3531 34
        if ($strict) {
3532 34
            if (self::$SUPPORT['finfo'] === false) {
3533
                throw new \RuntimeException('ext-fileinfo: is not installed');
3534
            }
3535
3536
            /**
3537
             * @noinspection   PhpComposerExtensionStubsInspection
3538
             * @psalm-suppress ImpureMethodCall - it will return the same result for the same file ...
3539
             */
3540 34
            $finfo_encoding = (new \finfo(\FILEINFO_MIME_ENCODING))->buffer($input);
3541 34
            if ($finfo_encoding && $finfo_encoding === 'binary') {
3542 15
                return true;
3543
            }
3544
        }
3545
3546 30
        return false;
3547
    }
3548
3549
    /**
3550
     * Check if the file is binary.
3551
     *
3552
     * @param string $file
3553
     *
3554
     * @return bool
3555
     */
3556 6
    public static function is_binary_file($file): bool
3557
    {
3558
        // init
3559 6
        $block = '';
3560
3561 6
        $fp = \fopen($file, 'rb');
3562 6
        if (\is_resource($fp)) {
3563 6
            $block = \fread($fp, 512);
3564 6
            \fclose($fp);
3565
        }
3566
3567 6
        if ($block === '') {
3568 2
            return false;
3569
        }
3570
3571 6
        return self::is_binary($block, true);
3572
    }
3573
3574
    /**
3575
     * Returns true if the string contains only whitespace chars, false otherwise.
3576
     *
3577
     * @param string $str <p>The input string.</p>
3578
     *
3579
     * @psalm-pure
3580
     *
3581
     * @return bool
3582
     *              <p>Whether or not $str contains only whitespace characters.</p>
3583
     */
3584 15
    public static function is_blank(string $str): bool
3585
    {
3586 15
        if (self::$SUPPORT['mbstring'] === true) {
3587
            /** @noinspection PhpComposerExtensionStubsInspection */
3588 15
            return \mb_ereg_match('^[[:space:]]*$', $str);
3589
        }
3590
3591
        return self::str_matches_pattern($str, '^[[:space:]]*$');
3592
    }
3593
3594
    /**
3595
     * Checks if the given string is equal to any "Byte Order Mark".
3596
     *
3597
     * WARNING: Use "UTF8::string_has_bom()" if you will check BOM in a string.
3598
     *
3599
     * @param string $str <p>The input string.</p>
3600
     *
3601
     * @psalm-pure
3602
     *
3603
     * @return bool
3604
     *              <p><strong>true</strong> if the $utf8_chr is Byte Order Mark, <strong>false</strong> otherwise.</p>
3605
     */
3606 2
    public static function is_bom($str): bool
3607
    {
3608
        /** @noinspection PhpUnusedLocalVariableInspection */
3609 2
        foreach (self::$BOM as $bom_string => &$bom_byte_length) {
3610 2
            if ($str === $bom_string) {
3611 2
                return true;
3612
            }
3613
        }
3614
3615 2
        return false;
3616
    }
3617
3618
    /**
3619
     * Determine whether the string is considered to be empty.
3620
     *
3621
     * A variable is considered empty if it does not exist or if its value equals FALSE.
3622
     * empty() does not generate a warning if the variable does not exist.
3623
     *
3624
     * @param mixed $str
3625
     *
3626
     * @psalm-pure
3627
     *
3628
     * @return bool
3629
     *              <p>Whether or not $str is empty().</p>
3630
     */
3631 1
    public static function is_empty($str): bool
3632
    {
3633 1
        return empty($str);
3634
    }
3635
3636
    /**
3637
     * Returns true if the string contains only hexadecimal chars, false otherwise.
3638
     *
3639
     * @param string $str <p>The input string.</p>
3640
     *
3641
     * @psalm-pure
3642
     *
3643
     * @return bool
3644
     *              <p>Whether or not $str contains only hexadecimal chars.</p>
3645
     */
3646 13
    public static function is_hexadecimal(string $str): bool
3647
    {
3648 13
        if (self::$SUPPORT['mbstring'] === true) {
3649
            /** @noinspection PhpComposerExtensionStubsInspection */
3650 13
            return \mb_ereg_match('^[[:xdigit:]]*$', $str);
3651
        }
3652
3653
        return self::str_matches_pattern($str, '^[[:xdigit:]]*$');
3654
    }
3655
3656
    /**
3657
     * Check if the string contains any HTML tags.
3658
     *
3659
     * @param string $str <p>The input string.</p>
3660
     *
3661
     * @psalm-pure
3662
     *
3663
     * @return bool
3664
     *              <p>Whether or not $str contains html elements.</p>
3665
     */
3666 3
    public static function is_html(string $str): bool
3667
    {
3668 3
        if ($str === '') {
3669 3
            return false;
3670
        }
3671
3672
        // init
3673 3
        $matches = [];
3674
3675 3
        $str = self::emoji_encode($str); // hack for emoji support :/
3676
3677 3
        \preg_match("/<\\/?\\w+(?:(?:\\s+\\w+(?:\\s*=\\s*(?:\".*?\"|'.*?'|[^'\">\\s]+))?)*\\s*|\\s*)\\/?>/u", $str, $matches);
3678
3679 3
        return $matches !== [];
3680
    }
3681
3682
    /**
3683
     * Check if $url is an correct url.
3684
     *
3685
     * @param string $url
3686
     * @param bool   $disallow_localhost
3687
     *
3688
     * @psalm-pure
3689
     *
3690
     * @return bool
3691
     */
3692 1
    public static function is_url(string $url, bool $disallow_localhost = false): bool
3693
    {
3694 1
        if ($url === '') {
3695 1
            return false;
3696
        }
3697
3698
        // WARNING: keep this as hack protection
3699 1
        if (!self::str_istarts_with_any($url, ['http://', 'https://'])) {
3700 1
            return false;
3701
        }
3702
3703
        // e.g. -> the server itself connect to "https://foo.localhost/phpmyadmin/...
3704 1
        if ($disallow_localhost) {
3705 1
            if (self::str_istarts_with_any(
3706 1
                $url,
3707
                [
3708 1
                    'http://localhost',
3709
                    'https://localhost',
3710
                    'http://127.0.0.1',
3711
                    'https://127.0.0.1',
3712
                    'http://::1',
3713
                    'https://::1',
3714
                ]
3715
            )) {
3716 1
                return false;
3717
            }
3718
3719 1
            $regex = '/^(?:http(?:s)?:\/\/).*?(?:\.localhost)/iu';
3720
            /** @noinspection BypassedUrlValidationInspection */
3721 1
            if (\preg_match($regex, $url)) {
3722 1
                return false;
3723
            }
3724
        }
3725
3726
        // INFO: this is needed for e.g. "http://müller.de/" (internationalized domain names) and non ASCII-parameters
3727
        /** @noinspection SuspiciousAssignmentsInspection - false-positive - https://github.com/kalessil/phpinspectionsea/issues/1500 */
3728 1
        $regex = '/^(?:http(?:s)?:\\/\\/)(?:[\p{L}0-9][\p{L}0-9_-]*(?:\\.[\p{L}0-9][\p{L}0-9_-]*))(?:\\d+)?(?:\\/\\.*)?/iu';
3729
        /** @noinspection BypassedUrlValidationInspection */
3730 1
        if (\preg_match($regex, $url)) {
3731 1
            return true;
3732
        }
3733
3734
        /** @noinspection BypassedUrlValidationInspection */
3735 1
        return \filter_var($url, \FILTER_VALIDATE_URL) !== false;
3736
    }
3737
3738
    /**
3739
     * Try to check if "$str" is a JSON-string.
3740
     *
3741
     * @param string $str                                    <p>The input string.</p>
3742
     * @param bool   $only_array_or_object_results_are_valid [optional] <p>Only array and objects are valid json
3743
     *                                                       results.</p>
3744
     *
3745
     * @return bool
3746
     *              <p>Whether or not the $str is in JSON format.</p>
3747
     */
3748 42
    public static function is_json(string $str, bool $only_array_or_object_results_are_valid = true): bool
3749
    {
3750 42
        if ($str === '') {
3751 4
            return false;
3752
        }
3753
3754 40
        if (self::$SUPPORT['json'] === false) {
3755
            throw new \RuntimeException('ext-json: is not installed');
3756
        }
3757
3758 40
        $jsonOrNull = self::json_decode($str);
3759 40
        if ($jsonOrNull === null && \strtoupper($str) !== 'NULL') {
3760 18
            return false;
3761
        }
3762
3763
        if (
3764 24
            $only_array_or_object_results_are_valid
3765
            &&
3766 24
            !\is_object($jsonOrNull)
3767
            &&
3768 24
            !\is_array($jsonOrNull)
3769
        ) {
3770 5
            return false;
3771
        }
3772
3773
        /** @noinspection PhpComposerExtensionStubsInspection */
3774 19
        return \json_last_error() === \JSON_ERROR_NONE;
3775
    }
3776
3777
    /**
3778
     * @param string $str <p>The input string.</p>
3779
     *
3780
     * @psalm-pure
3781
     *
3782
     * @return bool
3783
     *              <p>Whether or not $str contains only lowercase chars.</p>
3784
     */
3785 8
    public static function is_lowercase(string $str): bool
3786
    {
3787 8
        if (self::$SUPPORT['mbstring'] === true) {
3788
            /** @noinspection PhpComposerExtensionStubsInspection */
3789 8
            return \mb_ereg_match('^[[:lower:]]*$', $str);
3790
        }
3791
3792
        return self::str_matches_pattern($str, '^[[:lower:]]*$');
3793
    }
3794
3795
    /**
3796
     * Returns true if the string is serialized, false otherwise.
3797
     *
3798
     * @param string $str <p>The input string.</p>
3799
     *
3800
     * @psalm-pure
3801
     *
3802
     * @return bool
3803
     *              <p>Whether or not $str is serialized.</p>
3804
     */
3805 7
    public static function is_serialized(string $str): bool
3806
    {
3807 7
        if ($str === '') {
3808 1
            return false;
3809
        }
3810
3811
        /** @noinspection PhpUsageOfSilenceOperatorInspection */
3812
        /** @noinspection UnserializeExploitsInspection */
3813 6
        return $str === 'b:0;'
3814
               ||
3815 6
               @\unserialize($str) !== false;
3816
    }
3817
3818
    /**
3819
     * Returns true if the string contains only lower case chars, false
3820
     * otherwise.
3821
     *
3822
     * @param string $str <p>The input string.</p>
3823
     *
3824
     * @psalm-pure
3825
     *
3826
     * @return bool
3827
     *              <p>Whether or not $str contains only lower case characters.</p>
3828
     */
3829 8
    public static function is_uppercase(string $str): bool
3830
    {
3831 8
        if (self::$SUPPORT['mbstring'] === true) {
3832
            /** @noinspection PhpComposerExtensionStubsInspection */
3833 8
            return \mb_ereg_match('^[[:upper:]]*$', $str);
3834
        }
3835
3836
        return self::str_matches_pattern($str, '^[[:upper:]]*$');
3837
    }
3838
3839
    /**
3840
     * Check if the string is UTF-16.
3841
     *
3842
     * @param mixed $str                       <p>The input string.</p>
3843
     * @param bool  $check_if_string_is_binary
3844
     *
3845
     * @psalm-pure
3846
     *
3847
     * @return false|int
3848
     *                   <strong>false</strong> if is't not UTF-16,<br>
3849
     *                   <strong>1</strong> for UTF-16LE,<br>
3850
     *                   <strong>2</strong> for UTF-16BE
3851
     */
3852 22
    public static function is_utf16($str, bool $check_if_string_is_binary = true)
3853
    {
3854
        // init
3855 22
        $str = (string) $str;
3856 22
        $str_chars = [];
3857
3858
        if (
3859 22
            $check_if_string_is_binary
3860
            &&
3861 22
            !self::is_binary($str, true)
3862
        ) {
3863 2
            return false;
3864
        }
3865
3866 22
        if (self::$SUPPORT['mbstring'] === false) {
3867
            /**
3868
             * @psalm-suppress ImpureFunctionCall - is is only a warning
3869
             */
3870 3
            \trigger_error('UTF8::is_utf16() without mbstring may did not work correctly', \E_USER_WARNING);
3871
        }
3872
3873 22
        $str = self::remove_bom($str);
3874
3875 22
        $maybe_utf16le = 0;
3876 22
        $test = \mb_convert_encoding($str, 'UTF-8', 'UTF-16LE');
3877 22
        if ($test) {
3878 15
            $test2 = \mb_convert_encoding($test, 'UTF-16LE', 'UTF-8');
3879 15
            $test3 = \mb_convert_encoding($test2, 'UTF-8', 'UTF-16LE');
3880 15
            if ($test3 === $test) {
3881
                /**
3882
                 * @psalm-suppress RedundantCondition
3883
                 */
3884 15
                if ($str_chars === []) {
3885 15
                    $str_chars = self::count_chars($str, true, false);
3886
                }
3887 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...
3888 15
                    if (\in_array($test3char, $str_chars, true)) {
3889 15
                        ++$maybe_utf16le;
3890
                    }
3891
                }
3892 15
                unset($test3charEmpty);
3893
            }
3894
        }
3895
3896 22
        $maybe_utf16be = 0;
3897 22
        $test = \mb_convert_encoding($str, 'UTF-8', 'UTF-16BE');
3898 22
        if ($test) {
3899 15
            $test2 = \mb_convert_encoding($test, 'UTF-16BE', 'UTF-8');
3900 15
            $test3 = \mb_convert_encoding($test2, 'UTF-8', 'UTF-16BE');
3901 15
            if ($test3 === $test) {
3902 15
                if ($str_chars === []) {
3903 7
                    $str_chars = self::count_chars($str, true, false);
3904
                }
3905 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...
3906 15
                    if (\in_array($test3char, $str_chars, true)) {
3907 15
                        ++$maybe_utf16be;
3908
                    }
3909
                }
3910 15
                unset($test3charEmpty);
3911
            }
3912
        }
3913
3914 22
        if ($maybe_utf16be !== $maybe_utf16le) {
3915 7
            if ($maybe_utf16le > $maybe_utf16be) {
3916 5
                return 1;
3917
            }
3918
3919 6
            return 2;
3920
        }
3921
3922 18
        return false;
3923
    }
3924
3925
    /**
3926
     * Check if the string is UTF-32.
3927
     *
3928
     * @param mixed $str                       <p>The input string.</p>
3929
     * @param bool  $check_if_string_is_binary
3930
     *
3931
     * @psalm-pure
3932
     *
3933
     * @return false|int
3934
     *                   <strong>false</strong> if is't not UTF-32,<br>
3935
     *                   <strong>1</strong> for UTF-32LE,<br>
3936
     *                   <strong>2</strong> for UTF-32BE
3937
     */
3938 20
    public static function is_utf32($str, bool $check_if_string_is_binary = true)
3939
    {
3940
        // init
3941 20
        $str = (string) $str;
3942 20
        $str_chars = [];
3943
3944
        if (
3945 20
            $check_if_string_is_binary
3946
            &&
3947 20
            !self::is_binary($str, true)
3948
        ) {
3949 2
            return false;
3950
        }
3951
3952 20
        if (self::$SUPPORT['mbstring'] === false) {
3953
            /**
3954
             * @psalm-suppress ImpureFunctionCall - is is only a warning
3955
             */
3956 3
            \trigger_error('UTF8::is_utf32() without mbstring may did not work correctly', \E_USER_WARNING);
3957
        }
3958
3959 20
        $str = self::remove_bom($str);
3960
3961 20
        $maybe_utf32le = 0;
3962 20
        $test = \mb_convert_encoding($str, 'UTF-8', 'UTF-32LE');
3963 20
        if ($test) {
3964 13
            $test2 = \mb_convert_encoding($test, 'UTF-32LE', 'UTF-8');
3965 13
            $test3 = \mb_convert_encoding($test2, 'UTF-8', 'UTF-32LE');
3966 13
            if ($test3 === $test) {
3967
                /**
3968
                 * @psalm-suppress RedundantCondition
3969
                 */
3970 13
                if ($str_chars === []) {
3971 13
                    $str_chars = self::count_chars($str, true, false);
3972
                }
3973 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...
3974 13
                    if (\in_array($test3char, $str_chars, true)) {
3975 13
                        ++$maybe_utf32le;
3976
                    }
3977
                }
3978 13
                unset($test3charEmpty);
3979
            }
3980
        }
3981
3982 20
        $maybe_utf32be = 0;
3983 20
        $test = \mb_convert_encoding($str, 'UTF-8', 'UTF-32BE');
3984 20
        if ($test) {
3985 13
            $test2 = \mb_convert_encoding($test, 'UTF-32BE', 'UTF-8');
3986 13
            $test3 = \mb_convert_encoding($test2, 'UTF-8', 'UTF-32BE');
3987 13
            if ($test3 === $test) {
3988 13
                if ($str_chars === []) {
3989 7
                    $str_chars = self::count_chars($str, true, false);
3990
                }
3991 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...
3992 13
                    if (\in_array($test3char, $str_chars, true)) {
3993 13
                        ++$maybe_utf32be;
3994
                    }
3995
                }
3996 13
                unset($test3charEmpty);
3997
            }
3998
        }
3999
4000 20
        if ($maybe_utf32be !== $maybe_utf32le) {
4001 3
            if ($maybe_utf32le > $maybe_utf32be) {
4002 2
                return 1;
4003
            }
4004
4005 3
            return 2;
4006
        }
4007
4008 20
        return false;
4009
    }
4010
4011
    /**
4012
     * Checks whether the passed input contains only byte sequences that appear valid UTF-8.
4013
     *
4014
     * @param int|string|string[]|null $str    <p>The input to be checked.</p>
4015
     * @param bool                     $strict <p>Check also if the string is not UTF-16 or UTF-32.</p>
4016
     *
4017
     * @psalm-pure
4018
     *
4019
     * @return bool
4020
     */
4021 82
    public static function is_utf8($str, bool $strict = false): bool
4022
    {
4023 82
        if (\is_array($str)) {
4024 2
            foreach ($str as &$v) {
4025 2
                if (!self::is_utf8($v, $strict)) {
4026 2
                    return false;
4027
                }
4028
            }
4029
4030
            return true;
4031
        }
4032
4033 82
        return self::is_utf8_string((string) $str, $strict);
4034
    }
4035
4036
    /**
4037
     * (PHP 5 &gt;= 5.2.0, PECL json &gt;= 1.2.0)<br/>
4038
     * Decodes a JSON string
4039
     *
4040
     * @see http://php.net/manual/en/function.json-decode.php
4041
     *
4042
     * @param string $json    <p>
4043
     *                        The <i>json</i> string being decoded.
4044
     *                        </p>
4045
     *                        <p>
4046
     *                        This function only works with UTF-8 encoded strings.
4047
     *                        </p>
4048
     *                        <p>PHP implements a superset of
4049
     *                        JSON - it will also encode and decode scalar types and <b>NULL</b>. The JSON standard
4050
     *                        only supports these values when they are nested inside an array or an object.
4051
     *                        </p>
4052
     * @param bool   $assoc   [optional] <p>
4053
     *                        When <b>TRUE</b>, returned objects will be converted into
4054
     *                        associative arrays.
4055
     *                        </p>
4056
     * @param int    $depth   [optional] <p>
4057
     *                        User specified recursion depth.
4058
     *                        </p>
4059
     * @param int    $options [optional] <p>
4060
     *                        Bitmask of JSON decode options. Currently only
4061
     *                        <b>JSON_BIGINT_AS_STRING</b>
4062
     *                        is supported (default is to cast large integers as floats)
4063
     *                        </p>
4064
     *
4065
     * @psalm-pure
4066
     *
4067
     * @return mixed
4068
     *               The value encoded in <i>json</i> in appropriate PHP type. Values true, false and
4069
     *               null (case-insensitive) are returned as <b>TRUE</b>, <b>FALSE</b> and <b>NULL</b> respectively.
4070
     *               <b>NULL</b> is returned if the <i>json</i> cannot be decoded or if the encoded data
4071
     *               is deeper than the recursion limit.
4072
     */
4073 43
    public static function json_decode(
4074
        string $json,
4075
        bool $assoc = false,
4076
        int $depth = 512,
4077
        int $options = 0
4078
    ) {
4079 43
        $json = self::filter($json);
4080
4081 43
        if (self::$SUPPORT['json'] === false) {
4082
            throw new \RuntimeException('ext-json: is not installed');
4083
        }
4084
4085
        /** @noinspection PhpComposerExtensionStubsInspection */
4086 43
        return \json_decode($json, $assoc, $depth, $options);
4087
    }
4088
4089
    /**
4090
     * (PHP 5 &gt;= 5.2.0, PECL json &gt;= 1.2.0)<br/>
4091
     * Returns the JSON representation of a value.
4092
     *
4093
     * @see http://php.net/manual/en/function.json-encode.php
4094
     *
4095
     * @param mixed $value   <p>
4096
     *                       The <i>value</i> being encoded. Can be any type except
4097
     *                       a resource.
4098
     *                       </p>
4099
     *                       <p>
4100
     *                       All string data must be UTF-8 encoded.
4101
     *                       </p>
4102
     *                       <p>PHP implements a superset of
4103
     *                       JSON - it will also encode and decode scalar types and <b>NULL</b>. The JSON standard
4104
     *                       only supports these values when they are nested inside an array or an object.
4105
     *                       </p>
4106
     * @param int   $options [optional] <p>
4107
     *                       Bitmask consisting of <b>JSON_HEX_QUOT</b>,
4108
     *                       <b>JSON_HEX_TAG</b>,
4109
     *                       <b>JSON_HEX_AMP</b>,
4110
     *                       <b>JSON_HEX_APOS</b>,
4111
     *                       <b>JSON_NUMERIC_CHECK</b>,
4112
     *                       <b>JSON_PRETTY_PRINT</b>,
4113
     *                       <b>JSON_UNESCAPED_SLASHES</b>,
4114
     *                       <b>JSON_FORCE_OBJECT</b>,
4115
     *                       <b>JSON_UNESCAPED_UNICODE</b>. The behaviour of these
4116
     *                       constants is described on
4117
     *                       the JSON constants page.
4118
     *                       </p>
4119
     * @param int   $depth   [optional] <p>
4120
     *                       Set the maximum depth. Must be greater than zero.
4121
     *                       </p>
4122
     *
4123
     * @psalm-pure
4124
     *
4125
     * @return false|string
4126
     *                      A JSON encoded <strong>string</strong> on success or<br>
4127
     *                      <strong>FALSE</strong> on failure
4128
     */
4129 5
    public static function json_encode($value, int $options = 0, int $depth = 512)
4130
    {
4131 5
        $value = self::filter($value);
4132
4133 5
        if (self::$SUPPORT['json'] === false) {
4134
            throw new \RuntimeException('ext-json: is not installed');
4135
        }
4136
4137
        /** @noinspection PhpComposerExtensionStubsInspection */
4138 5
        return \json_encode($value, $options, $depth);
4139
    }
4140
4141
    /**
4142
     * Checks whether JSON is available on the server.
4143
     *
4144
     * @psalm-pure
4145
     *
4146
     * @return bool
4147
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
4148
     */
4149
    public static function json_loaded(): bool
4150
    {
4151
        return \function_exists('json_decode');
4152
    }
4153
4154
    /**
4155
     * Makes string's first char lowercase.
4156
     *
4157
     * @param string      $str                           <p>The input string</p>
4158
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
4159
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
4160
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt,
4161
     *                                                   tr</p>
4162
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ
4163
     *                                                   -> ß</p>
4164
     *
4165
     * @psalm-pure
4166
     *
4167
     * @return string the resulting string
4168
     */
4169 46
    public static function lcfirst(
4170
        string $str,
4171
        string $encoding = 'UTF-8',
4172
        bool $clean_utf8 = false,
4173
        string $lang = null,
4174
        bool $try_to_keep_the_string_length = false
4175
    ): string {
4176 46
        if ($clean_utf8) {
4177
            $str = self::clean($str);
4178
        }
4179
4180 46
        $use_mb_functions = ($lang === null && !$try_to_keep_the_string_length);
4181
4182 46
        if ($encoding === 'UTF-8') {
4183 43
            $str_part_two = (string) \mb_substr($str, 1);
4184
4185 43
            if ($use_mb_functions) {
4186 43
                $str_part_one = \mb_strtolower(
4187 43
                    (string) \mb_substr($str, 0, 1)
4188
                );
4189
            } else {
4190
                $str_part_one = self::strtolower(
4191
                    (string) \mb_substr($str, 0, 1),
4192
                    $encoding,
4193
                    false,
4194
                    $lang,
4195 43
                    $try_to_keep_the_string_length
4196
                );
4197
            }
4198
        } else {
4199 3
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
4200
4201 3
            $str_part_two = (string) self::substr($str, 1, null, $encoding);
4202
4203 3
            $str_part_one = self::strtolower(
4204 3
                (string) self::substr($str, 0, 1, $encoding),
4205 3
                $encoding,
4206 3
                false,
4207 3
                $lang,
4208 3
                $try_to_keep_the_string_length
4209
            );
4210
        }
4211
4212 46
        return $str_part_one . $str_part_two;
4213
    }
4214
4215
    /**
4216
     * alias for "UTF8::lcfirst()"
4217
     *
4218
     * @param string      $str
4219
     * @param string      $encoding
4220
     * @param bool        $clean_utf8
4221
     * @param string|null $lang
4222
     * @param bool        $try_to_keep_the_string_length
4223
     *
4224
     * @psalm-pure
4225
     *
4226
     * @return string
4227
     *
4228
     * @see        UTF8::lcfirst()
4229
     * @deprecated <p>please use "UTF8::lcfirst()"</p>
4230
     */
4231 2
    public static function lcword(
4232
        string $str,
4233
        string $encoding = 'UTF-8',
4234
        bool $clean_utf8 = false,
4235
        string $lang = null,
4236
        bool $try_to_keep_the_string_length = false
4237
    ): string {
4238 2
        return self::lcfirst(
4239 2
            $str,
4240 2
            $encoding,
4241 2
            $clean_utf8,
4242 2
            $lang,
4243 2
            $try_to_keep_the_string_length
4244
        );
4245
    }
4246
4247
    /**
4248
     * Lowercase for all words in the string.
4249
     *
4250
     * @param string      $str                           <p>The input string.</p>
4251
     * @param string[]    $exceptions                    [optional] <p>Exclusion for some words.</p>
4252
     * @param string      $char_list                     [optional] <p>Additional chars that contains to words and do
4253
     *                                                   not start a new word.</p>
4254
     * @param string      $encoding                      [optional] <p>Set the charset.</p>
4255
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
4256
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt,
4257
     *                                                   tr</p>
4258
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ
4259
     *                                                   -> ß</p>
4260
     *
4261
     * @psalm-pure
4262
     *
4263
     * @return string
4264
     */
4265 2
    public static function lcwords(
4266
        string $str,
4267
        array $exceptions = [],
4268
        string $char_list = '',
4269
        string $encoding = 'UTF-8',
4270
        bool $clean_utf8 = false,
4271
        string $lang = null,
4272
        bool $try_to_keep_the_string_length = false
4273
    ): string {
4274 2
        if (!$str) {
4275 2
            return '';
4276
        }
4277
4278 2
        $words = self::str_to_words($str, $char_list);
4279 2
        $use_exceptions = $exceptions !== [];
4280
4281 2
        $words_str = '';
4282 2
        foreach ($words as &$word) {
4283 2
            if (!$word) {
4284 2
                continue;
4285
            }
4286
4287
            if (
4288 2
                !$use_exceptions
4289
                ||
4290 2
                !\in_array($word, $exceptions, true)
4291
            ) {
4292 2
                $words_str .= self::lcfirst($word, $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
4293
            } else {
4294 2
                $words_str .= $word;
4295
            }
4296
        }
4297
4298 2
        return $words_str;
4299
    }
4300
4301
    /**
4302
     * alias for "UTF8::lcfirst()"
4303
     *
4304
     * @param string      $str
4305
     * @param string      $encoding
4306
     * @param bool        $clean_utf8
4307
     * @param string|null $lang
4308
     * @param bool        $try_to_keep_the_string_length
4309
     *
4310
     * @psalm-pure
4311
     *
4312
     * @return string
4313
     *
4314
     * @see        UTF8::lcfirst()
4315
     * @deprecated <p>please use "UTF8::lcfirst()"</p>
4316
     */
4317 5
    public static function lowerCaseFirst(
4318
        string $str,
4319
        string $encoding = 'UTF-8',
4320
        bool $clean_utf8 = false,
4321
        string $lang = null,
4322
        bool $try_to_keep_the_string_length = false
4323
    ): string {
4324 5
        return self::lcfirst(
4325 5
            $str,
4326 5
            $encoding,
4327 5
            $clean_utf8,
4328 5
            $lang,
4329 5
            $try_to_keep_the_string_length
4330
        );
4331
    }
4332
4333
    /**
4334
     * Strip whitespace or other characters from the beginning of a UTF-8 string.
4335
     *
4336
     * @param string      $str   <p>The string to be trimmed</p>
4337
     * @param string|null $chars <p>Optional characters to be stripped</p>
4338
     *
4339
     * @psalm-pure
4340
     *
4341
     * @return string the string with unwanted characters stripped from the left
4342
     */
4343 22
    public static function ltrim(string $str = '', string $chars = null): string
4344
    {
4345 22
        if ($str === '') {
4346 3
            return '';
4347
        }
4348
4349 21
        if (self::$SUPPORT['mbstring'] === true) {
4350 21
            if ($chars) {
4351
                /** @noinspection PregQuoteUsageInspection */
4352 10
                $chars = \preg_quote($chars);
4353 10
                $pattern = "^[${chars}]+";
4354
            } else {
4355 14
                $pattern = '^[\\s]+';
4356
            }
4357
4358
            /** @noinspection PhpComposerExtensionStubsInspection */
4359 21
            return (string) \mb_ereg_replace($pattern, '', $str);
4360
        }
4361
4362
        if ($chars) {
4363
            $chars = \preg_quote($chars, '/');
4364
            $pattern = "^[${chars}]+";
4365
        } else {
4366
            $pattern = '^[\\s]+';
4367
        }
4368
4369
        return self::regex_replace($str, $pattern, '', '', '/');
4370
    }
4371
4372
    /**
4373
     * Returns the UTF-8 character with the maximum code point in the given data.
4374
     *
4375
     * @param array<string>|string $arg <p>A UTF-8 encoded string or an array of such strings.</p>
4376
     *
4377
     * @psalm-pure
4378
     *
4379
     * @return string|null the character with the highest code point than others, returns null on failure or empty input
4380
     */
4381 2
    public static function max($arg)
4382
    {
4383 2
        if (\is_array($arg)) {
4384 2
            $arg = \implode('', $arg);
4385
        }
4386
4387 2
        $codepoints = self::codepoints($arg, false);
4388 2
        if ($codepoints === []) {
4389 2
            return null;
4390
        }
4391
4392 2
        $codepoint_max = \max($codepoints);
4393
4394 2
        return self::chr($codepoint_max);
4395
    }
4396
4397
    /**
4398
     * Calculates and returns the maximum number of bytes taken by any
4399
     * UTF-8 encoded character in the given string.
4400
     *
4401
     * @param string $str <p>The original Unicode string.</p>
4402
     *
4403
     * @psalm-pure
4404
     *
4405
     * @return int
4406
     *             <p>Max byte lengths of the given chars.</p>
4407
     */
4408 2
    public static function max_chr_width(string $str): int
4409
    {
4410 2
        $bytes = self::chr_size_list($str);
4411 2
        if ($bytes !== []) {
4412 2
            return (int) \max($bytes);
4413
        }
4414
4415 2
        return 0;
4416
    }
4417
4418
    /**
4419
     * Checks whether mbstring is available on the server.
4420
     *
4421
     * @psalm-pure
4422
     *
4423
     * @return bool
4424
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
4425
     */
4426 26
    public static function mbstring_loaded(): bool
4427
    {
4428 26
        return \extension_loaded('mbstring');
4429
    }
4430
4431
    /**
4432
     * Returns the UTF-8 character with the minimum code point in the given data.
4433
     *
4434
     * @param mixed $arg <strong>A UTF-8 encoded string or an array of such strings.</strong>
4435
     *
4436
     * @psalm-pure
4437
     *
4438
     * @return string|null the character with the lowest code point than others, returns null on failure or empty input
4439
     */
4440 2
    public static function min($arg)
4441
    {
4442 2
        if (\is_array($arg)) {
4443 2
            $arg = \implode('', $arg);
4444
        }
4445
4446 2
        $codepoints = self::codepoints($arg, false);
4447 2
        if ($codepoints === []) {
4448 2
            return null;
4449
        }
4450
4451 2
        $codepoint_min = \min($codepoints);
4452
4453 2
        return self::chr($codepoint_min);
4454
    }
4455
4456
    /**
4457
     * alias for "UTF8::normalize_encoding()"
4458
     *
4459
     * @param mixed $encoding
4460
     * @param mixed $fallback
4461
     *
4462
     * @psalm-pure
4463
     *
4464
     * @return mixed
4465
     *
4466
     * @see        UTF8::normalize_encoding()
4467
     * @deprecated <p>please use "UTF8::normalize_encoding()"</p>
4468
     */
4469 2
    public static function normalizeEncoding($encoding, $fallback = '')
4470
    {
4471 2
        return self::normalize_encoding($encoding, $fallback);
4472
    }
4473
4474
    /**
4475
     * Normalize the encoding-"name" input.
4476
     *
4477
     * @param mixed $encoding <p>e.g.: ISO, UTF8, WINDOWS-1251 etc.</p>
4478
     * @param mixed $fallback <p>e.g.: UTF-8</p>
4479
     *
4480
     * @psalm-pure
4481
     *
4482
     * @return mixed e.g.: ISO-8859-1, UTF-8, WINDOWS-1251 etc.<br>Will return a empty string as fallback (by default)
4483
     */
4484 339
    public static function normalize_encoding($encoding, $fallback = '')
4485
    {
4486
        /**
4487
         * @psalm-suppress ImpureStaticVariable
4488
         *
4489
         * @var array<string,string>
4490
         */
4491 339
        static $STATIC_NORMALIZE_ENCODING_CACHE = [];
4492
4493
        // init
4494 339
        $encoding = (string) $encoding;
4495
4496 339
        if (!$encoding) {
4497 290
            return $fallback;
4498
        }
4499
4500
        if (
4501 53
            $encoding === 'UTF-8'
4502
            ||
4503 53
            $encoding === 'UTF8'
4504
        ) {
4505 29
            return 'UTF-8';
4506
        }
4507
4508
        if (
4509 44
            $encoding === '8BIT'
4510
            ||
4511 44
            $encoding === 'BINARY'
4512
        ) {
4513
            return 'CP850';
4514
        }
4515
4516
        if (
4517 44
            $encoding === 'HTML'
4518
            ||
4519 44
            $encoding === 'HTML-ENTITIES'
4520
        ) {
4521 2
            return 'HTML-ENTITIES';
4522
        }
4523
4524
        if (
4525 44
            $encoding === 'ISO'
4526
            ||
4527 44
            $encoding === 'ISO-8859-1'
4528
        ) {
4529 41
            return 'ISO-8859-1';
4530
        }
4531
4532
        if (
4533 11
            $encoding === '1' // only a fallback, for non "strict_types" usage ...
4534
            ||
4535 11
            $encoding === '0' // only a fallback, for non "strict_types" usage ...
4536
        ) {
4537
            return $fallback;
4538
        }
4539
4540 11
        if (isset($STATIC_NORMALIZE_ENCODING_CACHE[$encoding])) {
4541 8
            return $STATIC_NORMALIZE_ENCODING_CACHE[$encoding];
4542
        }
4543
4544 5
        if (self::$ENCODINGS === null) {
4545 1
            self::$ENCODINGS = self::getData('encodings');
4546
        }
4547
4548 5
        if (\in_array($encoding, self::$ENCODINGS, true)) {
4549 3
            $STATIC_NORMALIZE_ENCODING_CACHE[$encoding] = $encoding;
4550
4551 3
            return $encoding;
4552
        }
4553
4554 4
        $encoding_original = $encoding;
4555 4
        $encoding = \strtoupper($encoding);
4556 4
        $encoding_upper_helper = (string) \preg_replace('/[^a-zA-Z0-9]/u', '', $encoding);
4557
4558
        $equivalences = [
4559 4
            'ISO8859'     => 'ISO-8859-1',
4560
            'ISO88591'    => 'ISO-8859-1',
4561
            'ISO'         => 'ISO-8859-1',
4562
            'LATIN'       => 'ISO-8859-1',
4563
            'LATIN1'      => 'ISO-8859-1', // Western European
4564
            'ISO88592'    => 'ISO-8859-2',
4565
            'LATIN2'      => 'ISO-8859-2', // Central European
4566
            'ISO88593'    => 'ISO-8859-3',
4567
            'LATIN3'      => 'ISO-8859-3', // Southern European
4568
            'ISO88594'    => 'ISO-8859-4',
4569
            'LATIN4'      => 'ISO-8859-4', // Northern European
4570
            'ISO88595'    => 'ISO-8859-5',
4571
            'ISO88596'    => 'ISO-8859-6', // Greek
4572
            'ISO88597'    => 'ISO-8859-7',
4573
            'ISO88598'    => 'ISO-8859-8', // Hebrew
4574
            'ISO88599'    => 'ISO-8859-9',
4575
            'LATIN5'      => 'ISO-8859-9', // Turkish
4576
            'ISO885911'   => 'ISO-8859-11',
4577
            'TIS620'      => 'ISO-8859-11', // Thai
4578
            'ISO885910'   => 'ISO-8859-10',
4579
            'LATIN6'      => 'ISO-8859-10', // Nordic
4580
            'ISO885913'   => 'ISO-8859-13',
4581
            'LATIN7'      => 'ISO-8859-13', // Baltic
4582
            'ISO885914'   => 'ISO-8859-14',
4583
            'LATIN8'      => 'ISO-8859-14', // Celtic
4584
            'ISO885915'   => 'ISO-8859-15',
4585
            'LATIN9'      => 'ISO-8859-15', // Western European (with some extra chars e.g. €)
4586
            'ISO885916'   => 'ISO-8859-16',
4587
            'LATIN10'     => 'ISO-8859-16', // Southeast European
4588
            'CP1250'      => 'WINDOWS-1250',
4589
            'WIN1250'     => 'WINDOWS-1250',
4590
            'WINDOWS1250' => 'WINDOWS-1250',
4591
            'CP1251'      => 'WINDOWS-1251',
4592
            'WIN1251'     => 'WINDOWS-1251',
4593
            'WINDOWS1251' => 'WINDOWS-1251',
4594
            'CP1252'      => 'WINDOWS-1252',
4595
            'WIN1252'     => 'WINDOWS-1252',
4596
            'WINDOWS1252' => 'WINDOWS-1252',
4597
            'CP1253'      => 'WINDOWS-1253',
4598
            'WIN1253'     => 'WINDOWS-1253',
4599
            'WINDOWS1253' => 'WINDOWS-1253',
4600
            'CP1254'      => 'WINDOWS-1254',
4601
            'WIN1254'     => 'WINDOWS-1254',
4602
            'WINDOWS1254' => 'WINDOWS-1254',
4603
            'CP1255'      => 'WINDOWS-1255',
4604
            'WIN1255'     => 'WINDOWS-1255',
4605
            'WINDOWS1255' => 'WINDOWS-1255',
4606
            'CP1256'      => 'WINDOWS-1256',
4607
            'WIN1256'     => 'WINDOWS-1256',
4608
            'WINDOWS1256' => 'WINDOWS-1256',
4609
            'CP1257'      => 'WINDOWS-1257',
4610
            'WIN1257'     => 'WINDOWS-1257',
4611
            'WINDOWS1257' => 'WINDOWS-1257',
4612
            'CP1258'      => 'WINDOWS-1258',
4613
            'WIN1258'     => 'WINDOWS-1258',
4614
            'WINDOWS1258' => 'WINDOWS-1258',
4615
            'UTF16'       => 'UTF-16',
4616
            'UTF32'       => 'UTF-32',
4617
            'UTF8'        => 'UTF-8',
4618
            'UTF'         => 'UTF-8',
4619
            'UTF7'        => 'UTF-7',
4620
            '8BIT'        => 'CP850',
4621
            'BINARY'      => 'CP850',
4622
        ];
4623
4624 4
        if (!empty($equivalences[$encoding_upper_helper])) {
4625 3
            $encoding = $equivalences[$encoding_upper_helper];
4626
        }
4627
4628 4
        $STATIC_NORMALIZE_ENCODING_CACHE[$encoding_original] = $encoding;
4629
4630 4
        return $encoding;
4631
    }
4632
4633
    /**
4634
     * Standardize line ending to unix-like.
4635
     *
4636
     * @param string          $str      <p>The input string.</p>
4637
     * @param string|string[] $replacer <p>The replacer char e.g. "\n" (Linux) or "\r\n" (Windows). You can also use \PHP_EOL
4638
     *                                  here.</p>
4639
     *
4640
     * @psalm-pure
4641
     *
4642
     * @return string
4643
     *                <p>A string with normalized line ending.</p>
4644
     */
4645 5
    public static function normalize_line_ending(string $str, $replacer = "\n"): string
4646
    {
4647 5
        return \str_replace(["\r\n", "\r", "\n"], $replacer, $str);
4648
    }
4649
4650
    /**
4651
     * Normalize some MS Word special characters.
4652
     *
4653
     * @param string $str <p>The string to be normalized.</p>
4654
     *
4655
     * @psalm-pure
4656
     *
4657
     * @return string
4658
     *                <p>A string with normalized characters for commonly used chars in Word documents.</p>
4659
     */
4660 10
    public static function normalize_msword(string $str): string
4661
    {
4662 10
        return ASCII::normalize_msword($str);
4663
    }
4664
4665
    /**
4666
     * Normalize the whitespace.
4667
     *
4668
     * @param string $str                        <p>The string to be normalized.</p>
4669
     * @param bool   $keep_non_breaking_space    [optional] <p>Set to true, to keep non-breaking-spaces.</p>
4670
     * @param bool   $keep_bidi_unicode_controls [optional] <p>Set to true, to keep non-printable (for the web)
4671
     *                                           bidirectional text chars.</p>
4672
     *
4673
     * @psalm-pure
4674
     *
4675
     * @return string
4676
     *                <p>A string with normalized whitespace.</p>
4677
     */
4678 61
    public static function normalize_whitespace(
4679
        string $str,
4680
        bool $keep_non_breaking_space = false,
4681
        bool $keep_bidi_unicode_controls = false
4682
    ): string {
4683 61
        return ASCII::normalize_whitespace(
4684 61
            $str,
4685 61
            $keep_non_breaking_space,
4686 61
            $keep_bidi_unicode_controls
4687
        );
4688
    }
4689
4690
    /**
4691
     * Calculates Unicode code point of the given UTF-8 encoded character.
4692
     *
4693
     * INFO: opposite to UTF8::chr()
4694
     *
4695
     * @param string $chr      <p>The character of which to calculate code point.<p/>
4696
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
4697
     *
4698
     * @psalm-pure
4699
     *
4700
     * @return int
4701
     *             <p>Unicode code point of the given character,<br>
4702
     *             0 on invalid UTF-8 byte sequence</p>
4703
     */
4704 27
    public static function ord($chr, string $encoding = 'UTF-8'): int
4705
    {
4706
        /**
4707
         * @psalm-suppress ImpureStaticVariable
4708
         *
4709
         * @var array<string,int>
4710
         */
4711 27
        static $CHAR_CACHE = [];
4712
4713
        // init
4714 27
        $chr = (string) $chr;
4715
4716 27
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
4717 5
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
4718
        }
4719
4720 27
        $cache_key = $chr . '_' . $encoding;
4721 27
        if (isset($CHAR_CACHE[$cache_key])) {
4722 27
            return $CHAR_CACHE[$cache_key];
4723
        }
4724
4725
        // check again, if it's still not UTF-8
4726 11
        if ($encoding !== 'UTF-8') {
4727 3
            $chr = self::encode($encoding, $chr);
4728
        }
4729
4730 11
        if (self::$ORD === null) {
4731
            self::$ORD = self::getData('ord');
4732
        }
4733
4734 11
        if (isset(self::$ORD[$chr])) {
4735 11
            return $CHAR_CACHE[$cache_key] = self::$ORD[$chr];
4736
        }
4737
4738
        //
4739
        // fallback via "IntlChar"
4740
        //
4741
4742 6
        if (self::$SUPPORT['intlChar'] === true) {
4743
            /** @noinspection PhpComposerExtensionStubsInspection */
4744 5
            $code = \IntlChar::ord($chr);
4745 5
            if ($code) {
4746 5
                return $CHAR_CACHE[$cache_key] = $code;
4747
            }
4748
        }
4749
4750
        //
4751
        // fallback via vanilla php
4752
        //
4753
4754
        /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
4755 1
        $chr = \unpack('C*', (string) \substr($chr, 0, 4));
4756
        /** @noinspection OffsetOperationsInspection */
4757 1
        $code = $chr ? $chr[1] : 0;
4758
4759
        /** @noinspection OffsetOperationsInspection */
4760 1
        if ($code >= 0xF0 && isset($chr[4])) {
4761
            /** @noinspection UnnecessaryCastingInspection */
4762
            /** @noinspection OffsetOperationsInspection */
4763
            return $CHAR_CACHE[$cache_key] = (int) ((($code - 0xF0) << 18) + (($chr[2] - 0x80) << 12) + (($chr[3] - 0x80) << 6) + $chr[4] - 0x80);
4764
        }
4765
4766
        /** @noinspection OffsetOperationsInspection */
4767 1
        if ($code >= 0xE0 && isset($chr[3])) {
4768
            /** @noinspection UnnecessaryCastingInspection */
4769
            /** @noinspection OffsetOperationsInspection */
4770 1
            return $CHAR_CACHE[$cache_key] = (int) ((($code - 0xE0) << 12) + (($chr[2] - 0x80) << 6) + $chr[3] - 0x80);
4771
        }
4772
4773
        /** @noinspection OffsetOperationsInspection */
4774 1
        if ($code >= 0xC0 && isset($chr[2])) {
4775
            /** @noinspection UnnecessaryCastingInspection */
4776
            /** @noinspection OffsetOperationsInspection */
4777 1
            return $CHAR_CACHE[$cache_key] = (int) ((($code - 0xC0) << 6) + $chr[2] - 0x80);
4778
        }
4779
4780
        return $CHAR_CACHE[$cache_key] = $code;
4781
    }
4782
4783
    /**
4784
     * Parses the string into an array (into the the second parameter).
4785
     *
4786
     * WARNING: Unlike "parse_str()", this method does not (re-)place variables in the current scope,
4787
     *          if the second parameter is not set!
4788
     *
4789
     * @see http://php.net/manual/en/function.parse-str.php
4790
     *
4791
     * @param string $str        <p>The input string.</p>
4792
     * @param array  $result     <p>The result will be returned into this reference parameter.</p>
4793
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
4794
     *
4795
     * @psalm-pure
4796
     *
4797
     * @return bool
4798
     *              <p>Will return <strong>false</strong> if php can't parse the string and we haven't any $result.</p>
4799
     */
4800 2
    public static function parse_str(string $str, &$result, bool $clean_utf8 = false): bool
4801
    {
4802 2
        if ($clean_utf8) {
4803 2
            $str = self::clean($str);
4804
        }
4805
4806 2
        if (self::$SUPPORT['mbstring'] === true) {
4807 2
            $return = \mb_parse_str($str, $result);
4808
4809 2
            return $return !== false && $result !== [];
4810
        }
4811
4812
        /**
4813
         * @psalm-suppress ImpureFunctionCall - we use the second parameter, so we don't change variables by magic
4814
         */
4815
        \parse_str($str, $result);
4816
4817
        return $result !== [];
4818
    }
4819
4820
    /**
4821
     * Checks if \u modifier is available that enables Unicode support in PCRE.
4822
     *
4823
     * @psalm-pure
4824
     *
4825
     * @return bool
4826
     *              <p>
4827
     *              <strong>true</strong> if support is available,<br>
4828
     *              <strong>false</strong> otherwise
4829
     *              </p>
4830
     */
4831 102
    public static function pcre_utf8_support(): bool
4832
    {
4833
        /** @noinspection PhpUsageOfSilenceOperatorInspection */
4834 102
        return (bool) @\preg_match('//u', '');
4835
    }
4836
4837
    /**
4838
     * Create an array containing a range of UTF-8 characters.
4839
     *
4840
     * @param mixed     $var1      <p>Numeric or hexadecimal code points, or a UTF-8 character to start from.</p>
4841
     * @param mixed     $var2      <p>Numeric or hexadecimal code points, or a UTF-8 character to end at.</p>
4842
     * @param bool      $use_ctype <p>use ctype to detect numeric and hexadecimal, otherwise we will use a simple
4843
     *                             "is_numeric"</p>
4844
     * @param string    $encoding  [optional] <p>Set the charset for e.g. "mb_" function</p>
4845
     * @param float|int $step      [optional] <p>
4846
     *                             If a step value is given, it will be used as the
4847
     *                             increment between elements in the sequence. step
4848
     *                             should be given as a positive number. If not specified,
4849
     *                             step will default to 1.
4850
     *                             </p>
4851
     *
4852
     * @psalm-pure
4853
     *
4854
     * @return string[]
4855
     */
4856 2
    public static function range(
4857
        $var1,
4858
        $var2,
4859
        bool $use_ctype = true,
4860
        string $encoding = 'UTF-8',
4861
        $step = 1
4862
    ): array {
4863 2
        if (!$var1 || !$var2) {
4864 2
            return [];
4865
        }
4866
4867 2
        if ($step !== 1) {
4868
            /**
4869
             * @psalm-suppress RedundantConditionGivenDocblockType
4870
             * @psalm-suppress DocblockTypeContradiction
4871
             */
4872 1
            if (!\is_numeric($step)) {
0 ignored issues
show
introduced by
The condition is_numeric($step) is always true.
Loading history...
4873
                throw new \InvalidArgumentException('$step need to be a number, type given: ' . \gettype($step));
4874
            }
4875
4876
            /**
4877
             * @psalm-suppress RedundantConditionGivenDocblockType - false-positive from psalm?
4878
             */
4879 1
            if ($step <= 0) {
4880
                throw new \InvalidArgumentException('$step need to be a positive number, given: ' . $step);
4881
            }
4882
        }
4883
4884 2
        if ($use_ctype && self::$SUPPORT['ctype'] === false) {
4885
            throw new \RuntimeException('ext-ctype: is not installed');
4886
        }
4887
4888 2
        $is_digit = false;
4889 2
        $is_xdigit = false;
4890
4891
        /** @noinspection PhpComposerExtensionStubsInspection */
4892 2
        if ($use_ctype && \ctype_digit((string) $var1) && \ctype_digit((string) $var2)) {
4893 2
            $is_digit = true;
4894 2
            $start = (int) $var1;
4895 2
        } /** @noinspection PhpComposerExtensionStubsInspection */ elseif ($use_ctype && \ctype_xdigit($var1) && \ctype_xdigit($var2)) {
4896
            $is_xdigit = true;
4897
            $start = (int) self::hex_to_int($var1);
4898 2
        } elseif (!$use_ctype && \is_numeric($var1)) {
4899 1
            $start = (int) $var1;
4900
        } else {
4901 2
            $start = self::ord($var1);
4902
        }
4903
4904 2
        if (!$start) {
4905
            return [];
4906
        }
4907
4908 2
        if ($is_digit) {
4909 2
            $end = (int) $var2;
4910 2
        } elseif ($is_xdigit) {
4911
            $end = (int) self::hex_to_int($var2);
4912 2
        } elseif (!$use_ctype && \is_numeric($var2)) {
4913 1
            $end = (int) $var2;
4914
        } else {
4915 2
            $end = self::ord($var2);
4916
        }
4917
4918 2
        if (!$end) {
4919
            return [];
4920
        }
4921
4922 2
        $array = [];
4923 2
        foreach (\range($start, $end, $step) as $i) {
4924 2
            $array[] = (string) self::chr((int) $i, $encoding);
4925
        }
4926
4927 2
        return $array;
4928
    }
4929
4930
    /**
4931
     * Multi decode HTML entity + fix urlencoded-win1252-chars.
4932
     *
4933
     * e.g:
4934
     * 'test+test'                     => 'test+test'
4935
     * 'D&#252;sseldorf'               => 'Düsseldorf'
4936
     * 'D%FCsseldorf'                  => 'Düsseldorf'
4937
     * 'D&#xFC;sseldorf'               => 'Düsseldorf'
4938
     * 'D%26%23xFC%3Bsseldorf'         => 'Düsseldorf'
4939
     * 'Düsseldorf'                   => 'Düsseldorf'
4940
     * 'D%C3%BCsseldorf'               => 'Düsseldorf'
4941
     * 'D%C3%83%C2%BCsseldorf'         => 'Düsseldorf'
4942
     * 'D%25C3%2583%25C2%25BCsseldorf' => 'Düsseldorf'
4943
     *
4944
     * @param string $str          <p>The input string.</p>
4945
     * @param bool   $multi_decode <p>Decode as often as possible.</p>
4946
     *
4947
     * @psalm-pure
4948
     *
4949
     * @return string
4950
     *                <p>The decoded URL, as a string.</p>
4951
     */
4952 7
    public static function rawurldecode(string $str, bool $multi_decode = true): string
4953
    {
4954 7
        if ($str === '') {
4955 4
            return '';
4956
        }
4957
4958
        if (
4959 7
            \strpos($str, '&') === false
4960
            &&
4961 7
            \strpos($str, '%') === false
4962
            &&
4963 7
            \strpos($str, '+') === false
4964
            &&
4965 7
            \strpos($str, '\u') === false
4966
        ) {
4967 4
            return self::fix_simple_utf8($str);
4968
        }
4969
4970 7
        $str = self::urldecode_unicode_helper($str);
4971
4972 7
        if ($multi_decode) {
4973
            do {
4974 6
                $str_compare = $str;
4975
4976
                /**
4977
                 * @psalm-suppress PossiblyInvalidArgument
4978
                 */
4979 6
                $str = self::fix_simple_utf8(
4980 6
                    \rawurldecode(
4981 6
                        self::html_entity_decode(
4982 6
                            self::to_utf8($str),
4983 6
                            \ENT_QUOTES | \ENT_HTML5
4984
                        )
4985
                    )
4986
                );
4987 6
            } while ($str_compare !== $str);
4988
        } else {
4989
            /**
4990
             * @psalm-suppress PossiblyInvalidArgument
4991
             */
4992 1
            $str = self::fix_simple_utf8(
4993 1
                \rawurldecode(
4994 1
                    self::html_entity_decode(
4995 1
                        self::to_utf8($str),
4996 1
                        \ENT_QUOTES | \ENT_HTML5
4997
                    )
4998
                )
4999
            );
5000
        }
5001
5002 7
        return $str;
5003
    }
5004
5005
    /**
5006
     * Replaces all occurrences of $pattern in $str by $replacement.
5007
     *
5008
     * @param string $str         <p>The input string.</p>
5009
     * @param string $pattern     <p>The regular expression pattern.</p>
5010
     * @param string $replacement <p>The string to replace with.</p>
5011
     * @param string $options     [optional] <p>Matching conditions to be used.</p>
5012
     * @param string $delimiter   [optional] <p>Delimiter the the regex. Default: '/'</p>
5013
     *
5014
     * @psalm-pure
5015
     *
5016
     * @return string
5017
     */
5018 18
    public static function regex_replace(
5019
        string $str,
5020
        string $pattern,
5021
        string $replacement,
5022
        string $options = '',
5023
        string $delimiter = '/'
5024
    ): string {
5025 18
        if ($options === 'msr') {
5026 9
            $options = 'ms';
5027
        }
5028
5029
        // fallback
5030 18
        if (!$delimiter) {
5031
            $delimiter = '/';
5032
        }
5033
5034 18
        return (string) \preg_replace(
5035 18
            $delimiter . $pattern . $delimiter . 'u' . $options,
5036 18
            $replacement,
5037 18
            $str
5038
        );
5039
    }
5040
5041
    /**
5042
     * alias for "UTF8::remove_bom()"
5043
     *
5044
     * @param string $str
5045
     *
5046
     * @psalm-pure
5047
     *
5048
     * @return string
5049
     *
5050
     * @see        UTF8::remove_bom()
5051
     * @deprecated <p>please use "UTF8::remove_bom()"</p>
5052
     */
5053 1
    public static function removeBOM(string $str): string
5054
    {
5055 1
        return self::remove_bom($str);
5056
    }
5057
5058
    /**
5059
     * Remove the BOM from UTF-8 / UTF-16 / UTF-32 strings.
5060
     *
5061
     * @param string $str <p>The input string.</p>
5062
     *
5063
     * @psalm-pure
5064
     *
5065
     * @return string
5066
     *                <p>A string without UTF-BOM.</p>
5067
     */
5068 55
    public static function remove_bom(string $str): string
5069
    {
5070 55
        if ($str === '') {
5071 9
            return '';
5072
        }
5073
5074 55
        $str_length = \strlen($str);
5075 55
        foreach (self::$BOM as $bom_string => $bom_byte_length) {
5076 55
            if (\strpos($str, $bom_string, 0) === 0) {
5077
                /** @var false|string $str_tmp - needed for PhpStan (stubs error) */
5078 11
                $str_tmp = \substr($str, $bom_byte_length, $str_length);
5079 11
                if ($str_tmp === false) {
5080
                    return '';
5081
                }
5082
5083 11
                $str_length -= (int) $bom_byte_length;
5084
5085 55
                $str = (string) $str_tmp;
5086
            }
5087
        }
5088
5089 55
        return $str;
5090
    }
5091
5092
    /**
5093
     * Removes duplicate occurrences of a string in another string.
5094
     *
5095
     * @param string          $str  <p>The base string.</p>
5096
     * @param string|string[] $what <p>String to search for in the base string.</p>
5097
     *
5098
     * @psalm-pure
5099
     *
5100
     * @return string
5101
     *                <p>A string with removed duplicates.</p>
5102
     */
5103 2
    public static function remove_duplicates(string $str, $what = ' '): string
5104
    {
5105 2
        if (\is_string($what)) {
5106 2
            $what = [$what];
5107
        }
5108
5109
        /**
5110
         * @psalm-suppress RedundantConditionGivenDocblockType
5111
         */
5112 2
        if (\is_array($what)) {
0 ignored issues
show
introduced by
The condition is_array($what) is always true.
Loading history...
5113 2
            foreach ($what as $item) {
5114 2
                $str = (string) \preg_replace('/(' . \preg_quote($item, '/') . ')+/u', $item, $str);
5115
            }
5116
        }
5117
5118 2
        return $str;
5119
    }
5120
5121
    /**
5122
     * Remove html via "strip_tags()" from the string.
5123
     *
5124
     * @param string $str            <p>The input string.</p>
5125
     * @param string $allowable_tags [optional] <p>You can use the optional second parameter to specify tags which
5126
     *                               should not be stripped. Default: null
5127
     *                               </p>
5128
     *
5129
     * @psalm-pure
5130
     *
5131
     * @return string
5132
     *                <p>A string with without html tags.</p>
5133
     */
5134 6
    public static function remove_html(string $str, string $allowable_tags = ''): string
5135
    {
5136 6
        return \strip_tags($str, $allowable_tags);
5137
    }
5138
5139
    /**
5140
     * Remove all breaks [<br> | \r\n | \r | \n | ...] from the string.
5141
     *
5142
     * @param string $str         <p>The input string.</p>
5143
     * @param string $replacement [optional] <p>Default is a empty string.</p>
5144
     *
5145
     * @psalm-pure
5146
     *
5147
     * @return string
5148
     *                <p>A string without breaks.</p>
5149
     */
5150 6
    public static function remove_html_breaks(string $str, string $replacement = ''): string
5151
    {
5152 6
        return (string) \preg_replace("#/\r\n|\r|\n|<br.*/?>#isU", $replacement, $str);
5153
    }
5154
5155
    /**
5156
     * Remove invisible characters from a string.
5157
     *
5158
     * e.g.: This prevents sandwiching null characters between ascii characters, like Java\0script.
5159
     *
5160
     * copy&past from https://github.com/bcit-ci/CodeIgniter/blob/develop/system/core/Common.php
5161
     *
5162
     * @param string $str         <p>The input string.</p>
5163
     * @param bool   $url_encoded [optional] <p>
5164
     *                            Try to remove url encoded control character.
5165
     *                            WARNING: maybe contains false-positives e.g. aa%0Baa -> aaaa.
5166
     *                            <br>
5167
     *                            Default: false
5168
     *                            </p>
5169
     * @param string $replacement [optional] <p>The replacement character.</p>
5170
     *
5171
     * @psalm-pure
5172
     *
5173
     * @return string
5174
     *                <p>A string without invisible chars.</p>
5175
     */
5176 90
    public static function remove_invisible_characters(
5177
        string $str,
5178
        bool $url_encoded = false,
5179
        string $replacement = ''
5180
    ): string {
5181 90
        return ASCII::remove_invisible_characters(
5182 90
            $str,
5183 90
            $url_encoded,
5184 90
            $replacement
5185
        );
5186
    }
5187
5188
    /**
5189
     * Returns a new string with the prefix $substring removed, if present.
5190
     *
5191
     * @param string $str       <p>The input string.</p>
5192
     * @param string $substring <p>The prefix to remove.</p>
5193
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
5194
     *
5195
     * @psalm-pure
5196
     *
5197
     * @return string
5198
     *                <p>A string without the prefix $substring.</p>
5199
     */
5200 12
    public static function remove_left(
5201
        string $str,
5202
        string $substring,
5203
        string $encoding = 'UTF-8'
5204
    ): string {
5205 12
        if ($substring && \strpos($str, $substring) === 0) {
5206 6
            if ($encoding === 'UTF-8') {
5207 4
                return (string) \mb_substr(
5208 4
                    $str,
5209 4
                    (int) \mb_strlen($substring)
5210
                );
5211
            }
5212
5213 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
5214
5215 2
            return (string) self::substr(
5216 2
                $str,
5217 2
                (int) self::strlen($substring, $encoding),
5218 2
                null,
5219 2
                $encoding
5220
            );
5221
        }
5222
5223 6
        return $str;
5224
    }
5225
5226
    /**
5227
     * Returns a new string with the suffix $substring removed, if present.
5228
     *
5229
     * @param string $str
5230
     * @param string $substring <p>The suffix to remove.</p>
5231
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
5232
     *
5233
     * @psalm-pure
5234
     *
5235
     * @return string
5236
     *                <p>A string having a $str without the suffix $substring.</p>
5237
     */
5238 12
    public static function remove_right(
5239
        string $str,
5240
        string $substring,
5241
        string $encoding = 'UTF-8'
5242
    ): string {
5243 12
        if ($substring && \substr($str, -\strlen($substring)) === $substring) {
5244 6
            if ($encoding === 'UTF-8') {
5245 4
                return (string) \mb_substr(
5246 4
                    $str,
5247 4
                    0,
5248 4
                    (int) \mb_strlen($str) - (int) \mb_strlen($substring)
5249
                );
5250
            }
5251
5252 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
5253
5254 2
            return (string) self::substr(
5255 2
                $str,
5256 2
                0,
5257 2
                (int) self::strlen($str, $encoding) - (int) self::strlen($substring, $encoding),
5258 2
                $encoding
5259
            );
5260
        }
5261
5262 6
        return $str;
5263
    }
5264
5265
    /**
5266
     * Replaces all occurrences of $search in $str by $replacement.
5267
     *
5268
     * @param string $str            <p>The input string.</p>
5269
     * @param string $search         <p>The needle to search for.</p>
5270
     * @param string $replacement    <p>The string to replace with.</p>
5271
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
5272
     *
5273
     * @psalm-pure
5274
     *
5275
     * @return string
5276
     *                <p>A string with replaced parts.</p>
5277
     */
5278 29
    public static function replace(
5279
        string $str,
5280
        string $search,
5281
        string $replacement,
5282
        bool $case_sensitive = true
5283
    ): string {
5284 29
        if ($case_sensitive) {
5285 22
            return \str_replace($search, $replacement, $str);
5286
        }
5287
5288 7
        return self::str_ireplace($search, $replacement, $str);
5289
    }
5290
5291
    /**
5292
     * Replaces all occurrences of $search in $str by $replacement.
5293
     *
5294
     * @param string       $str            <p>The input string.</p>
5295
     * @param array        $search         <p>The elements to search for.</p>
5296
     * @param array|string $replacement    <p>The string to replace with.</p>
5297
     * @param bool         $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
5298
     *
5299
     * @psalm-pure
5300
     *
5301
     * @return string
5302
     *                <p>A string with replaced parts.</p>
5303
     */
5304 30
    public static function replace_all(
5305
        string $str,
5306
        array $search,
5307
        $replacement,
5308
        bool $case_sensitive = true
5309
    ): string {
5310 30
        if ($case_sensitive) {
5311 23
            return \str_replace($search, $replacement, $str);
5312
        }
5313
5314 7
        return self::str_ireplace($search, $replacement, $str);
5315
    }
5316
5317
    /**
5318
     * Replace the diamond question mark (�) and invalid-UTF8 chars with the replacement.
5319
     *
5320
     * @param string $str                        <p>The input string</p>
5321
     * @param string $replacement_char           <p>The replacement character.</p>
5322
     * @param bool   $process_invalid_utf8_chars <p>Convert invalid UTF-8 chars </p>
5323
     *
5324
     * @psalm-pure
5325
     *
5326
     * @return string
5327
     *                <p>A string without diamond question marks (�).</p>
5328
     */
5329 35
    public static function replace_diamond_question_mark(
5330
        string $str,
5331
        string $replacement_char = '',
5332
        bool $process_invalid_utf8_chars = true
5333
    ): string {
5334 35
        if ($str === '') {
5335 9
            return '';
5336
        }
5337
5338 35
        if ($process_invalid_utf8_chars) {
5339 35
            $replacement_char_helper = $replacement_char;
5340 35
            if ($replacement_char === '') {
5341 35
                $replacement_char_helper = 'none';
5342
            }
5343
5344 35
            if (self::$SUPPORT['mbstring'] === false) {
5345
                // if there is no native support for "mbstring",
5346
                // then we need to clean the string before ...
5347
                $str = self::clean($str);
5348
            }
5349
5350
            /**
5351
             * @psalm-suppress ImpureFunctionCall - we will reset the value in the next step
5352
             */
5353 35
            $save = \mb_substitute_character();
5354 35
            \mb_substitute_character($replacement_char_helper);
5355
            // the polyfill maybe return false, so cast to string
5356 35
            $str = (string) \mb_convert_encoding($str, 'UTF-8', 'UTF-8');
5357 35
            \mb_substitute_character($save);
5358
        }
5359
5360 35
        return \str_replace(
5361
            [
5362 35
                "\xEF\xBF\xBD",
5363
                '�',
5364
            ],
5365
            [
5366 35
                $replacement_char,
5367 35
                $replacement_char,
5368
            ],
5369 35
            $str
5370
        );
5371
    }
5372
5373
    /**
5374
     * Strip whitespace or other characters from the end of a UTF-8 string.
5375
     *
5376
     * @param string      $str   <p>The string to be trimmed.</p>
5377
     * @param string|null $chars <p>Optional characters to be stripped.</p>
5378
     *
5379
     * @psalm-pure
5380
     *
5381
     * @return string
5382
     *                <p>A string with unwanted characters stripped from the right.</p>
5383
     */
5384 20
    public static function rtrim(string $str = '', string $chars = null): string
5385
    {
5386 20
        if ($str === '') {
5387 3
            return '';
5388
        }
5389
5390 19
        if (self::$SUPPORT['mbstring'] === true) {
5391 19
            if ($chars) {
5392
                /** @noinspection PregQuoteUsageInspection */
5393 8
                $chars = \preg_quote($chars);
5394 8
                $pattern = "[${chars}]+$";
5395
            } else {
5396 14
                $pattern = '[\\s]+$';
5397
            }
5398
5399
            /** @noinspection PhpComposerExtensionStubsInspection */
5400 19
            return (string) \mb_ereg_replace($pattern, '', $str);
5401
        }
5402
5403
        if ($chars) {
5404
            $chars = \preg_quote($chars, '/');
5405
            $pattern = "[${chars}]+$";
5406
        } else {
5407
            $pattern = '[\\s]+$';
5408
        }
5409
5410
        return self::regex_replace($str, $pattern, '', '', '/');
5411
    }
5412
5413
    /**
5414
     * WARNING: Print native UTF-8 support (libs) by default, e.g. for debugging.
5415
     *
5416
     * @param bool $useEcho
5417
     *
5418
     * @psalm-pure
5419
     *
5420
     * @return string|void
5421
     */
5422 2
    public static function showSupport(bool $useEcho = true)
5423
    {
5424
        // init
5425 2
        $html = '';
5426
5427 2
        $html .= '<pre>';
5428
        /** @noinspection AlterInForeachInspection */
5429 2
        foreach (self::$SUPPORT as $key => &$value) {
5430 2
            $html .= $key . ' - ' . \print_r($value, true) . "\n<br>";
5431
        }
5432 2
        $html .= '</pre>';
5433
5434 2
        if ($useEcho) {
5435 1
            echo $html;
5436
        }
5437
5438 2
        return $html;
5439
    }
5440
5441
    /**
5442
     * Converts a UTF-8 character to HTML Numbered Entity like "&#123;".
5443
     *
5444
     * @param string $char             <p>The Unicode character to be encoded as numbered entity.</p>
5445
     * @param bool   $keep_ascii_chars <p>Set to <strong>true</strong> to keep ASCII chars.</>
5446
     * @param string $encoding         [optional] <p>Set the charset for e.g. "mb_" function</p>
5447
     *
5448
     * @psalm-pure
5449
     *
5450
     * @return string
5451
     *                <p>The HTML numbered entity for the given character.</p>
5452
     */
5453 2
    public static function single_chr_html_encode(
5454
        string $char,
5455
        bool $keep_ascii_chars = false,
5456
        string $encoding = 'UTF-8'
5457
    ): string {
5458 2
        if ($char === '') {
5459 2
            return '';
5460
        }
5461
5462
        if (
5463 2
            $keep_ascii_chars
5464
            &&
5465 2
            ASCII::is_ascii($char)
5466
        ) {
5467 2
            return $char;
5468
        }
5469
5470 2
        return '&#' . self::ord($char, $encoding) . ';';
5471
    }
5472
5473
    /**
5474
     * @param string $str
5475
     * @param int    $tab_length
5476
     *
5477
     * @psalm-pure
5478
     *
5479
     * @return string
5480
     */
5481 5
    public static function spaces_to_tabs(string $str, int $tab_length = 4): string
5482
    {
5483 5
        if ($tab_length === 4) {
5484 3
            $tab = '    ';
5485 2
        } elseif ($tab_length === 2) {
5486 1
            $tab = '  ';
5487
        } else {
5488 1
            $tab = \str_repeat(' ', $tab_length);
5489
        }
5490
5491 5
        return \str_replace($tab, "\t", $str);
5492
    }
5493
5494
    /**
5495
     * alias for "UTF8::str_split()"
5496
     *
5497
     * @param int|string $str
5498
     * @param int        $length
5499
     * @param bool       $clean_utf8
5500
     *
5501
     * @psalm-pure
5502
     *
5503
     * @return string[]
5504
     *
5505
     * @see        UTF8::str_split()
5506
     * @deprecated <p>please use "UTF8::str_split()"</p>
5507
     */
5508 9
    public static function split(
5509
        $str,
5510
        int $length = 1,
5511
        bool $clean_utf8 = false
5512
    ): array {
5513
        /** @var string[] */
5514 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...
5515
    }
5516
5517
    /**
5518
     * alias for "UTF8::str_starts_with()"
5519
     *
5520
     * @param string $haystack
5521
     * @param string $needle
5522
     *
5523
     * @psalm-pure
5524
     *
5525
     * @return bool
5526
     *
5527
     * @see        UTF8::str_starts_with()
5528
     * @deprecated <p>please use "UTF8::str_starts_with()"</p>
5529
     */
5530 1
    public static function str_begins(string $haystack, string $needle): bool
5531
    {
5532 1
        return self::str_starts_with($haystack, $needle);
5533
    }
5534
5535
    /**
5536
     * Returns a camelCase version of the string. Trims surrounding spaces,
5537
     * capitalizes letters following digits, spaces, dashes and underscores,
5538
     * and removes spaces, dashes, as well as underscores.
5539
     *
5540
     * @param string      $str                           <p>The input string.</p>
5541
     * @param string      $encoding                      [optional] <p>Default: 'UTF-8'</p>
5542
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
5543
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt,
5544
     *                                                   tr</p>
5545
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ
5546
     *                                                   -> ß</p>
5547
     *
5548
     * @psalm-pure
5549
     *
5550
     * @return string
5551
     */
5552 32
    public static function str_camelize(
5553
        string $str,
5554
        string $encoding = 'UTF-8',
5555
        bool $clean_utf8 = false,
5556
        string $lang = null,
5557
        bool $try_to_keep_the_string_length = false
5558
    ): string {
5559 32
        if ($clean_utf8) {
5560
            $str = self::clean($str);
5561
        }
5562
5563 32
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
5564 26
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
5565
        }
5566
5567 32
        $str = self::lcfirst(
5568 32
            \trim($str),
5569 32
            $encoding,
5570 32
            false,
5571 32
            $lang,
5572 32
            $try_to_keep_the_string_length
5573
        );
5574 32
        $str = (string) \preg_replace('/^[-_]+/', '', $str);
5575
5576 32
        $use_mb_functions = $lang === null && !$try_to_keep_the_string_length;
5577
5578 32
        $str = (string) \preg_replace_callback(
5579 32
            '/[-_\\s]+(.)?/u',
5580
            /**
5581
             * @param array $match
5582
             *
5583
             * @psalm-pure
5584
             *
5585
             * @return string
5586
             */
5587
            static function (array $match) use ($use_mb_functions, $encoding, $lang, $try_to_keep_the_string_length): string {
5588 27
                if (isset($match[1])) {
5589 27
                    if ($use_mb_functions) {
5590 27
                        if ($encoding === 'UTF-8') {
5591 27
                            return \mb_strtoupper($match[1]);
5592
                        }
5593
5594
                        return \mb_strtoupper($match[1], $encoding);
5595
                    }
5596
5597
                    return self::strtoupper($match[1], $encoding, false, $lang, $try_to_keep_the_string_length);
5598
                }
5599
5600 1
                return '';
5601 32
            },
5602 32
            $str
5603
        );
5604
5605 32
        return (string) \preg_replace_callback(
5606 32
            '/[\\p{N}]+(.)?/u',
5607
            /**
5608
             * @param array $match
5609
             *
5610
             * @psalm-pure
5611
             *
5612
             * @return string
5613
             */
5614
            static function (array $match) use ($use_mb_functions, $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length): string {
5615 6
                if ($use_mb_functions) {
5616 6
                    if ($encoding === 'UTF-8') {
5617 6
                        return \mb_strtoupper($match[0]);
5618
                    }
5619
5620
                    return \mb_strtoupper($match[0], $encoding);
5621
                }
5622
5623
                return self::strtoupper($match[0], $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
5624 32
            },
5625 32
            $str
5626
        );
5627
    }
5628
5629
    /**
5630
     * Returns the string with the first letter of each word capitalized,
5631
     * except for when the word is a name which shouldn't be capitalized.
5632
     *
5633
     * @param string $str
5634
     *
5635
     * @psalm-pure
5636
     *
5637
     * @return string
5638
     *                <p>A string with $str capitalized.</p>
5639
     */
5640 1
    public static function str_capitalize_name(string $str): string
5641
    {
5642 1
        return self::str_capitalize_name_helper(
5643 1
            self::str_capitalize_name_helper(
5644 1
                self::collapse_whitespace($str),
5645 1
                ' '
5646
            ),
5647 1
            '-'
5648
        );
5649
    }
5650
5651
    /**
5652
     * Returns true if the string contains $needle, false otherwise. By default
5653
     * the comparison is case-sensitive, but can be made insensitive by setting
5654
     * $case_sensitive to false.
5655
     *
5656
     * @param string $haystack       <p>The input string.</p>
5657
     * @param string $needle         <p>Substring to look for.</p>
5658
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
5659
     *
5660
     * @psalm-pure
5661
     *
5662
     * @return bool whether or not $haystack contains $needle
5663
     */
5664 21
    public static function str_contains(
5665
        string $haystack,
5666
        string $needle,
5667
        bool $case_sensitive = true
5668
    ): bool {
5669 21
        if ($case_sensitive) {
5670 11
            return \strpos($haystack, $needle) !== false;
5671
        }
5672
5673 10
        return \mb_stripos($haystack, $needle) !== false;
5674
    }
5675
5676
    /**
5677
     * Returns true if the string contains all $needles, false otherwise. By
5678
     * default the comparison is case-sensitive, but can be made insensitive by
5679
     * setting $case_sensitive to false.
5680
     *
5681
     * @param string $haystack       <p>The input string.</p>
5682
     * @param array  $needles        <p>SubStrings to look for.</p>
5683
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
5684
     *
5685
     * @psalm-pure
5686
     *
5687
     * @return bool whether or not $haystack contains $needle
5688
     */
5689 45
    public static function str_contains_all(
5690
        string $haystack,
5691
        array $needles,
5692
        bool $case_sensitive = true
5693
    ): bool {
5694 45
        if ($haystack === '' || $needles === []) {
5695 1
            return false;
5696
        }
5697
5698
        /** @noinspection LoopWhichDoesNotLoopInspection */
5699 44
        foreach ($needles as &$needle) {
5700 44
            if ($case_sensitive) {
5701
                /** @noinspection NestedPositiveIfStatementsInspection */
5702 24
                if (!$needle || \strpos($haystack, $needle) === false) {
5703 12
                    return false;
5704
                }
5705
            }
5706
5707 33
            if (!$needle || \mb_stripos($haystack, $needle) === false) {
5708 33
                return false;
5709
            }
5710
        }
5711
5712 24
        return true;
5713
    }
5714
5715
    /**
5716
     * Returns true if the string contains any $needles, false otherwise. By
5717
     * default the comparison is case-sensitive, but can be made insensitive by
5718
     * setting $case_sensitive to false.
5719
     *
5720
     * @param string $haystack       <p>The input string.</p>
5721
     * @param array  $needles        <p>SubStrings to look for.</p>
5722
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
5723
     *
5724
     * @psalm-pure
5725
     *
5726
     * @return bool
5727
     *              Whether or not $str contains $needle
5728
     */
5729 46
    public static function str_contains_any(
5730
        string $haystack,
5731
        array $needles,
5732
        bool $case_sensitive = true
5733
    ): bool {
5734 46
        if ($haystack === '' || $needles === []) {
5735 1
            return false;
5736
        }
5737
5738
        /** @noinspection LoopWhichDoesNotLoopInspection */
5739 45
        foreach ($needles as &$needle) {
5740 45
            if (!$needle) {
5741
                continue;
5742
            }
5743
5744 45
            if ($case_sensitive) {
5745 25
                if (\strpos($haystack, $needle) !== false) {
5746 14
                    return true;
5747
                }
5748
5749 13
                continue;
5750
            }
5751
5752 20
            if (\mb_stripos($haystack, $needle) !== false) {
5753 20
                return true;
5754
            }
5755
        }
5756
5757 19
        return false;
5758
    }
5759
5760
    /**
5761
     * Returns a lowercase and trimmed string separated by dashes. Dashes are
5762
     * inserted before uppercase characters (with the exception of the first
5763
     * character of the string), and in place of spaces as well as underscores.
5764
     *
5765
     * @param string $str      <p>The input string.</p>
5766
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
5767
     *
5768
     * @psalm-pure
5769
     *
5770
     * @return string
5771
     */
5772 19
    public static function str_dasherize(string $str, string $encoding = 'UTF-8'): string
5773
    {
5774 19
        return self::str_delimit($str, '-', $encoding);
5775
    }
5776
5777
    /**
5778
     * Returns a lowercase and trimmed string separated by the given delimiter.
5779
     * Delimiters are inserted before uppercase characters (with the exception
5780
     * of the first character of the string), and in place of spaces, dashes,
5781
     * and underscores. Alpha delimiters are not converted to lowercase.
5782
     *
5783
     * @param string      $str                           <p>The input string.</p>
5784
     * @param string      $delimiter                     <p>Sequence used to separate parts of the string.</p>
5785
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
5786
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
5787
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt,
5788
     *                                                   tr</p>
5789
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ ->
5790
     *                                                   ß</p>
5791
     *
5792
     * @psalm-pure
5793
     *
5794
     * @return string
5795
     */
5796 49
    public static function str_delimit(
5797
        string $str,
5798
        string $delimiter,
5799
        string $encoding = 'UTF-8',
5800
        bool $clean_utf8 = false,
5801
        string $lang = null,
5802
        bool $try_to_keep_the_string_length = false
5803
    ): string {
5804 49
        if (self::$SUPPORT['mbstring'] === true) {
5805
            /** @noinspection PhpComposerExtensionStubsInspection */
5806 49
            $str = (string) \mb_ereg_replace('\\B(\\p{Lu})', '-\1', \trim($str));
5807
5808 49
            $use_mb_functions = $lang === null && !$try_to_keep_the_string_length;
5809 49
            if ($use_mb_functions && $encoding === 'UTF-8') {
5810 22
                $str = \mb_strtolower($str);
5811
            } else {
5812 27
                $str = self::strtolower($str, $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
5813
            }
5814
5815
            /** @noinspection PhpComposerExtensionStubsInspection */
5816 49
            return (string) \mb_ereg_replace('[\\-_\\s]+', $delimiter, $str);
5817
        }
5818
5819
        $str = (string) \preg_replace('/\\B(\\p{Lu})/u', '-\1', \trim($str));
5820
5821
        $use_mb_functions = $lang === null && !$try_to_keep_the_string_length;
5822
        if ($use_mb_functions && $encoding === 'UTF-8') {
5823
            $str = \mb_strtolower($str);
5824
        } else {
5825
            $str = self::strtolower($str, $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
5826
        }
5827
5828
        return (string) \preg_replace('/[\\-_\\s]+/u', $delimiter, $str);
5829
    }
5830
5831
    /**
5832
     * Optimized "mb_detect_encoding()"-function -> with support for UTF-16 and UTF-32.
5833
     *
5834
     * @param string $str <p>The input string.</p>
5835
     *
5836
     * @psalm-pure
5837
     *
5838
     * @return false|string
5839
     *                      The detected string-encoding e.g. UTF-8 or UTF-16BE,<br>
5840
     *                      otherwise it will return false e.g. for BINARY or not detected encoding.
5841
     */
5842 30
    public static function str_detect_encoding($str)
5843
    {
5844
        // init
5845 30
        $str = (string) $str;
5846
5847
        //
5848
        // 1.) check binary strings (010001001...) like UTF-16 / UTF-32 / PDF / Images / ...
5849
        //
5850
5851 30
        if (self::is_binary($str, true)) {
5852 11
            $is_utf32 = self::is_utf32($str, false);
5853 11
            if ($is_utf32 === 1) {
5854
                return 'UTF-32LE';
5855
            }
5856 11
            if ($is_utf32 === 2) {
5857 1
                return 'UTF-32BE';
5858
            }
5859
5860 11
            $is_utf16 = self::is_utf16($str, false);
5861 11
            if ($is_utf16 === 1) {
5862 3
                return 'UTF-16LE';
5863
            }
5864 11
            if ($is_utf16 === 2) {
5865 2
                return 'UTF-16BE';
5866
            }
5867
5868
            // is binary but not "UTF-16" or "UTF-32"
5869 9
            return false;
5870
        }
5871
5872
        //
5873
        // 2.) simple check for ASCII chars
5874
        //
5875
5876 26
        if (ASCII::is_ascii($str)) {
5877 10
            return 'ASCII';
5878
        }
5879
5880
        //
5881
        // 3.) simple check for UTF-8 chars
5882
        //
5883
5884 26
        if (self::is_utf8_string($str)) {
5885 19
            return 'UTF-8';
5886
        }
5887
5888
        //
5889
        // 4.) check via "mb_detect_encoding()"
5890
        //
5891
        // INFO: UTF-16, UTF-32, UCS2 and UCS4, encoding detection will fail always with "mb_detect_encoding()"
5892
5893
        $encoding_detecting_order = [
5894 15
            'ISO-8859-1',
5895
            'ISO-8859-2',
5896
            'ISO-8859-3',
5897
            'ISO-8859-4',
5898
            'ISO-8859-5',
5899
            'ISO-8859-6',
5900
            'ISO-8859-7',
5901
            'ISO-8859-8',
5902
            'ISO-8859-9',
5903
            'ISO-8859-10',
5904
            'ISO-8859-13',
5905
            'ISO-8859-14',
5906
            'ISO-8859-15',
5907
            'ISO-8859-16',
5908
            'WINDOWS-1251',
5909
            'WINDOWS-1252',
5910
            'WINDOWS-1254',
5911
            'CP932',
5912
            'CP936',
5913
            'CP950',
5914
            'CP866',
5915
            'CP850',
5916
            'CP51932',
5917
            'CP50220',
5918
            'CP50221',
5919
            'CP50222',
5920
            'ISO-2022-JP',
5921
            'ISO-2022-KR',
5922
            'JIS',
5923
            'JIS-ms',
5924
            'EUC-CN',
5925
            'EUC-JP',
5926
        ];
5927
5928 15
        if (self::$SUPPORT['mbstring'] === true) {
5929
            // info: do not use the symfony polyfill here
5930 15
            $encoding = \mb_detect_encoding($str, $encoding_detecting_order, true);
5931 15
            if ($encoding) {
5932 15
                return $encoding;
5933
            }
5934
        }
5935
5936
        //
5937
        // 5.) check via "iconv()"
5938
        //
5939
5940
        if (self::$ENCODINGS === null) {
5941
            self::$ENCODINGS = self::getData('encodings');
5942
        }
5943
5944
        foreach (self::$ENCODINGS as $encoding_tmp) {
5945
            // INFO: //IGNORE but still throw notice
5946
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
5947
            if ((string) @\iconv($encoding_tmp, $encoding_tmp . '//IGNORE', $str) === $str) {
5948
                return $encoding_tmp;
5949
            }
5950
        }
5951
5952
        return false;
5953
    }
5954
5955
    /**
5956
     * alias for "UTF8::str_ends_with()"
5957
     *
5958
     * @param string $haystack
5959
     * @param string $needle
5960
     *
5961
     * @psalm-pure
5962
     *
5963
     * @return bool
5964
     *
5965
     * @see        UTF8::str_ends_with()
5966
     * @deprecated <p>please use "UTF8::str_ends_with()"</p>
5967
     */
5968 1
    public static function str_ends(string $haystack, string $needle): bool
5969
    {
5970 1
        return self::str_ends_with($haystack, $needle);
5971
    }
5972
5973
    /**
5974
     * Check if the string ends with the given substring.
5975
     *
5976
     * @param string $haystack <p>The string to search in.</p>
5977
     * @param string $needle   <p>The substring to search for.</p>
5978
     *
5979
     * @psalm-pure
5980
     *
5981
     * @return bool
5982
     */
5983 9
    public static function str_ends_with(string $haystack, string $needle): bool
5984
    {
5985 9
        if ($needle === '') {
5986 2
            return true;
5987
        }
5988
5989 9
        if ($haystack === '') {
5990
            return false;
5991
        }
5992
5993 9
        return \substr($haystack, -\strlen($needle)) === $needle;
5994
    }
5995
5996
    /**
5997
     * Returns true if the string ends with any of $substrings, false otherwise.
5998
     *
5999
     * - case-sensitive
6000
     *
6001
     * @param string   $str        <p>The input string.</p>
6002
     * @param string[] $substrings <p>Substrings to look for.</p>
6003
     *
6004
     * @psalm-pure
6005
     *
6006
     * @return bool whether or not $str ends with $substring
6007
     */
6008 7
    public static function str_ends_with_any(string $str, array $substrings): bool
6009
    {
6010 7
        if ($substrings === []) {
6011
            return false;
6012
        }
6013
6014 7
        foreach ($substrings as &$substring) {
6015 7
            if (\substr($str, -\strlen($substring)) === $substring) {
6016 7
                return true;
6017
            }
6018
        }
6019
6020 6
        return false;
6021
    }
6022
6023
    /**
6024
     * Ensures that the string begins with $substring. If it doesn't, it's
6025
     * prepended.
6026
     *
6027
     * @param string $str       <p>The input string.</p>
6028
     * @param string $substring <p>The substring to add if not present.</p>
6029
     *
6030
     * @psalm-pure
6031
     *
6032
     * @return string
6033
     */
6034 10
    public static function str_ensure_left(string $str, string $substring): string
6035
    {
6036
        if (
6037 10
            $substring !== ''
6038
            &&
6039 10
            \strpos($str, $substring) === 0
6040
        ) {
6041 6
            return $str;
6042
        }
6043
6044 4
        return $substring . $str;
6045
    }
6046
6047
    /**
6048
     * Ensures that the string ends with $substring. If it doesn't, it's appended.
6049
     *
6050
     * @param string $str       <p>The input string.</p>
6051
     * @param string $substring <p>The substring to add if not present.</p>
6052
     *
6053
     * @psalm-pure
6054
     *
6055
     * @return string
6056
     */
6057 10
    public static function str_ensure_right(string $str, string $substring): string
6058
    {
6059
        if (
6060 10
            $str === ''
6061
            ||
6062 10
            $substring === ''
6063
            ||
6064 10
            \substr($str, -\strlen($substring)) !== $substring
6065
        ) {
6066 4
            $str .= $substring;
6067
        }
6068
6069 10
        return $str;
6070
    }
6071
6072
    /**
6073
     * Capitalizes the first word of the string, replaces underscores with
6074
     * spaces, and strips '_id'.
6075
     *
6076
     * @param string $str
6077
     *
6078
     * @psalm-pure
6079
     *
6080
     * @return string
6081
     */
6082 3
    public static function str_humanize($str): string
6083
    {
6084 3
        $str = \str_replace(
6085
            [
6086 3
                '_id',
6087
                '_',
6088
            ],
6089
            [
6090 3
                '',
6091
                ' ',
6092
            ],
6093 3
            $str
6094
        );
6095
6096 3
        return self::ucfirst(\trim($str));
6097
    }
6098
6099
    /**
6100
     * alias for "UTF8::str_istarts_with()"
6101
     *
6102
     * @param string $haystack
6103
     * @param string $needle
6104
     *
6105
     * @psalm-pure
6106
     *
6107
     * @return bool
6108
     *
6109
     * @see        UTF8::str_istarts_with()
6110
     * @deprecated <p>please use "UTF8::str_istarts_with()"</p>
6111
     */
6112 1
    public static function str_ibegins(string $haystack, string $needle): bool
6113
    {
6114 1
        return self::str_istarts_with($haystack, $needle);
6115
    }
6116
6117
    /**
6118
     * alias for "UTF8::str_iends_with()"
6119
     *
6120
     * @param string $haystack
6121
     * @param string $needle
6122
     *
6123
     * @psalm-pure
6124
     *
6125
     * @return bool
6126
     *
6127
     * @see        UTF8::str_iends_with()
6128
     * @deprecated <p>please use "UTF8::str_iends_with()"</p>
6129
     */
6130 1
    public static function str_iends(string $haystack, string $needle): bool
6131
    {
6132 1
        return self::str_iends_with($haystack, $needle);
6133
    }
6134
6135
    /**
6136
     * Check if the string ends with the given substring, case-insensitive.
6137
     *
6138
     * @param string $haystack <p>The string to search in.</p>
6139
     * @param string $needle   <p>The substring to search for.</p>
6140
     *
6141
     * @psalm-pure
6142
     *
6143
     * @return bool
6144
     */
6145 12
    public static function str_iends_with(string $haystack, string $needle): bool
6146
    {
6147 12
        if ($needle === '') {
6148 2
            return true;
6149
        }
6150
6151 12
        if ($haystack === '') {
6152
            return false;
6153
        }
6154
6155 12
        return self::strcasecmp(\substr($haystack, -\strlen($needle)), $needle) === 0;
6156
    }
6157
6158
    /**
6159
     * Returns true if the string ends with any of $substrings, false otherwise.
6160
     *
6161
     * - case-insensitive
6162
     *
6163
     * @param string   $str        <p>The input string.</p>
6164
     * @param string[] $substrings <p>Substrings to look for.</p>
6165
     *
6166
     * @psalm-pure
6167
     *
6168
     * @return bool
6169
     *              <p>Whether or not $str ends with $substring.</p>
6170
     */
6171 4
    public static function str_iends_with_any(string $str, array $substrings): bool
6172
    {
6173 4
        if ($substrings === []) {
6174
            return false;
6175
        }
6176
6177 4
        foreach ($substrings as &$substring) {
6178 4
            if (self::str_iends_with($str, $substring)) {
6179 4
                return true;
6180
            }
6181
        }
6182
6183
        return false;
6184
    }
6185
6186
    /**
6187
     * Returns the index of the first occurrence of $needle in the string,
6188
     * and false if not found. Accepts an optional offset from which to begin
6189
     * the search.
6190
     *
6191
     * @param string $str      <p>The input string.</p>
6192
     * @param string $needle   <p>Substring to look for.</p>
6193
     * @param int    $offset   [optional] <p>Offset from which to search. Default: 0</p>
6194
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6195
     *
6196
     * @psalm-pure
6197
     *
6198
     * @return false|int
6199
     *                   <p>The occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
6200
     *
6201
     * @see        UTF8::stripos()
6202
     * @deprecated <p>please use "UTF8::stripos()"</p>
6203
     */
6204 1
    public static function str_iindex_first(
6205
        string $str,
6206
        string $needle,
6207
        int $offset = 0,
6208
        string $encoding = 'UTF-8'
6209
    ) {
6210 1
        return self::stripos(
6211 1
            $str,
6212 1
            $needle,
6213 1
            $offset,
6214 1
            $encoding
6215
        );
6216
    }
6217
6218
    /**
6219
     * Returns the index of the last occurrence of $needle in the string,
6220
     * and false if not found. Accepts an optional offset from which to begin
6221
     * the search. Offsets may be negative to count from the last character
6222
     * in the string.
6223
     *
6224
     * @param string $str      <p>The input string.</p>
6225
     * @param string $needle   <p>Substring to look for.</p>
6226
     * @param int    $offset   [optional] <p>Offset from which to search. Default: 0</p>
6227
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6228
     *
6229
     * @psalm-pure
6230
     *
6231
     * @return false|int
6232
     *                   <p>The last occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
6233
     *
6234
     * @see        UTF8::strripos()
6235
     * @deprecated <p>please use "UTF8::strripos()"</p>
6236
     */
6237 10
    public static function str_iindex_last(
6238
        string $str,
6239
        string $needle,
6240
        int $offset = 0,
6241
        string $encoding = 'UTF-8'
6242
    ) {
6243 10
        return self::strripos(
6244 10
            $str,
6245 10
            $needle,
6246 10
            $offset,
6247 10
            $encoding
6248
        );
6249
    }
6250
6251
    /**
6252
     * Returns the index of the first occurrence of $needle in the string,
6253
     * and false if not found. Accepts an optional offset from which to begin
6254
     * the search.
6255
     *
6256
     * @param string $str      <p>The input string.</p>
6257
     * @param string $needle   <p>Substring to look for.</p>
6258
     * @param int    $offset   [optional] <p>Offset from which to search. Default: 0</p>
6259
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6260
     *
6261
     * @psalm-pure
6262
     *
6263
     * @return false|int
6264
     *                   <p>The occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
6265
     *
6266
     * @see        UTF8::strpos()
6267
     * @deprecated <p>please use "UTF8::strpos()"</p>
6268
     */
6269 11
    public static function str_index_first(
6270
        string $str,
6271
        string $needle,
6272
        int $offset = 0,
6273
        string $encoding = 'UTF-8'
6274
    ) {
6275 11
        return self::strpos(
6276 11
            $str,
6277 11
            $needle,
6278 11
            $offset,
6279 11
            $encoding
6280
        );
6281
    }
6282
6283
    /**
6284
     * Returns the index of the last occurrence of $needle in the string,
6285
     * and false if not found. Accepts an optional offset from which to begin
6286
     * the search. Offsets may be negative to count from the last character
6287
     * in the string.
6288
     *
6289
     * @param string $str      <p>The input string.</p>
6290
     * @param string $needle   <p>Substring to look for.</p>
6291
     * @param int    $offset   [optional] <p>Offset from which to search. Default: 0</p>
6292
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6293
     *
6294
     * @psalm-pure
6295
     *
6296
     * @return false|int
6297
     *                   <p>The last occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
6298
     *
6299
     * @see        UTF8::strrpos()
6300
     * @deprecated <p>please use "UTF8::strrpos()"</p>
6301
     */
6302 10
    public static function str_index_last(
6303
        string $str,
6304
        string $needle,
6305
        int $offset = 0,
6306
        string $encoding = 'UTF-8'
6307
    ) {
6308 10
        return self::strrpos(
6309 10
            $str,
6310 10
            $needle,
6311 10
            $offset,
6312 10
            $encoding
6313
        );
6314
    }
6315
6316
    /**
6317
     * Inserts $substring into the string at the $index provided.
6318
     *
6319
     * @param string $str       <p>The input string.</p>
6320
     * @param string $substring <p>String to be inserted.</p>
6321
     * @param int    $index     <p>The index at which to insert the substring.</p>
6322
     * @param string $encoding  [optional] <p>Set the charset for e.g. "mb_" function</p>
6323
     *
6324
     * @psalm-pure
6325
     *
6326
     * @return string
6327
     */
6328 8
    public static function str_insert(
6329
        string $str,
6330
        string $substring,
6331
        int $index,
6332
        string $encoding = 'UTF-8'
6333
    ): string {
6334 8
        if ($encoding === 'UTF-8') {
6335 4
            $len = (int) \mb_strlen($str);
6336 4
            if ($index > $len) {
6337
                return $str;
6338
            }
6339
6340
            /** @noinspection UnnecessaryCastingInspection */
6341 4
            return (string) \mb_substr($str, 0, $index) .
6342 4
                   $substring .
6343 4
                   (string) \mb_substr($str, $index, $len);
6344
        }
6345
6346 4
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
6347
6348 4
        $len = (int) self::strlen($str, $encoding);
6349 4
        if ($index > $len) {
6350 1
            return $str;
6351
        }
6352
6353 3
        return ((string) self::substr($str, 0, $index, $encoding)) .
6354 3
               $substring .
6355 3
               ((string) self::substr($str, $index, $len, $encoding));
6356
    }
6357
6358
    /**
6359
     * Case-insensitive and UTF-8 safe version of <function>str_replace</function>.
6360
     *
6361
     * @see http://php.net/manual/en/function.str-ireplace.php
6362
     *
6363
     * @param mixed $search  <p>
6364
     *                       Every replacement with search array is
6365
     *                       performed on the result of previous replacement.
6366
     *                       </p>
6367
     * @param mixed $replace <p>
6368
     *                       </p>
6369
     * @param mixed $subject <p>
6370
     *                       If subject is an array, then the search and
6371
     *                       replace is performed with every entry of
6372
     *                       subject, and the return value is an array as
6373
     *                       well.
6374
     *                       </p>
6375
     * @param int   $count   [optional] <p>
6376
     *                       The number of matched and replaced needles will
6377
     *                       be returned in count which is passed by
6378
     *                       reference.
6379
     *                       </p>
6380
     *
6381
     * @psalm-pure
6382
     *
6383
     * @return mixed a string or an array of replacements
6384
     */
6385 29
    public static function str_ireplace($search, $replace, $subject, &$count = null)
6386
    {
6387 29
        $search = (array) $search;
6388
6389
        /** @noinspection AlterInForeachInspection */
6390 29
        foreach ($search as &$s) {
6391 29
            $s = (string) $s;
6392 29
            if ($s === '') {
6393 6
                $s = '/^(?<=.)$/';
6394
            } else {
6395 29
                $s = '/' . \preg_quote($s, '/') . '/ui';
6396
            }
6397
        }
6398
6399 29
        $subject = \preg_replace($search, $replace, $subject, -1, $replace);
6400 29
        $count = $replace; // used as reference parameter
6401
6402 29
        return $subject;
6403
    }
6404
6405
    /**
6406
     * Replaces $search from the beginning of string with $replacement.
6407
     *
6408
     * @param string $str         <p>The input string.</p>
6409
     * @param string $search      <p>The string to search for.</p>
6410
     * @param string $replacement <p>The replacement.</p>
6411
     *
6412
     * @psalm-pure
6413
     *
6414
     * @return string string after the replacements
6415
     */
6416 17
    public static function str_ireplace_beginning(string $str, string $search, string $replacement): string
6417
    {
6418 17
        if ($str === '') {
6419 4
            if ($replacement === '') {
6420 2
                return '';
6421
            }
6422
6423 2
            if ($search === '') {
6424 2
                return $replacement;
6425
            }
6426
        }
6427
6428 13
        if ($search === '') {
6429 2
            return $str . $replacement;
6430
        }
6431
6432 11
        if (\stripos($str, $search) === 0) {
6433 10
            return $replacement . \substr($str, \strlen($search));
6434
        }
6435
6436 1
        return $str;
6437
    }
6438
6439
    /**
6440
     * Replaces $search from the ending of string with $replacement.
6441
     *
6442
     * @param string $str         <p>The input string.</p>
6443
     * @param string $search      <p>The string to search for.</p>
6444
     * @param string $replacement <p>The replacement.</p>
6445
     *
6446
     * @psalm-pure
6447
     *
6448
     * @return string
6449
     *                <p>string after the replacements.</p>
6450
     */
6451 17
    public static function str_ireplace_ending(string $str, string $search, string $replacement): string
6452
    {
6453 17
        if ($str === '') {
6454 4
            if ($replacement === '') {
6455 2
                return '';
6456
            }
6457
6458 2
            if ($search === '') {
6459 2
                return $replacement;
6460
            }
6461
        }
6462
6463 13
        if ($search === '') {
6464 2
            return $str . $replacement;
6465
        }
6466
6467 11
        if (\stripos($str, $search, \strlen($str) - \strlen($search)) !== false) {
6468 9
            $str = \substr($str, 0, -\strlen($search)) . $replacement;
6469
        }
6470
6471 11
        return $str;
6472
    }
6473
6474
    /**
6475
     * Check if the string starts with the given substring, case-insensitive.
6476
     *
6477
     * @param string $haystack <p>The string to search in.</p>
6478
     * @param string $needle   <p>The substring to search for.</p>
6479
     *
6480
     * @psalm-pure
6481
     *
6482
     * @return bool
6483
     */
6484 13
    public static function str_istarts_with(string $haystack, string $needle): bool
6485
    {
6486 13
        if ($needle === '') {
6487 2
            return true;
6488
        }
6489
6490 13
        if ($haystack === '') {
6491
            return false;
6492
        }
6493
6494 13
        return self::stripos($haystack, $needle) === 0;
6495
    }
6496
6497
    /**
6498
     * Returns true if the string begins with any of $substrings, false otherwise.
6499
     *
6500
     * - case-insensitive
6501
     *
6502
     * @param string $str        <p>The input string.</p>
6503
     * @param array  $substrings <p>Substrings to look for.</p>
6504
     *
6505
     * @psalm-pure
6506
     *
6507
     * @return bool whether or not $str starts with $substring
6508
     */
6509 5
    public static function str_istarts_with_any(string $str, array $substrings): bool
6510
    {
6511 5
        if ($str === '') {
6512
            return false;
6513
        }
6514
6515 5
        if ($substrings === []) {
6516
            return false;
6517
        }
6518
6519 5
        foreach ($substrings as &$substring) {
6520 5
            if (self::str_istarts_with($str, $substring)) {
6521 5
                return true;
6522
            }
6523
        }
6524
6525 1
        return false;
6526
    }
6527
6528
    /**
6529
     * Gets the substring after the first occurrence of a separator.
6530
     *
6531
     * @param string $str       <p>The input string.</p>
6532
     * @param string $separator <p>The string separator.</p>
6533
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
6534
     *
6535
     * @psalm-pure
6536
     *
6537
     * @return string
6538
     */
6539 1
    public static function str_isubstr_after_first_separator(
6540
        string $str,
6541
        string $separator,
6542
        string $encoding = 'UTF-8'
6543
    ): string {
6544 1
        if ($separator === '' || $str === '') {
6545 1
            return '';
6546
        }
6547
6548 1
        $offset = self::stripos($str, $separator);
6549 1
        if ($offset === false) {
6550 1
            return '';
6551
        }
6552
6553 1
        if ($encoding === 'UTF-8') {
6554 1
            return (string) \mb_substr(
6555 1
                $str,
6556 1
                $offset + (int) \mb_strlen($separator)
6557
            );
6558
        }
6559
6560
        return (string) self::substr(
6561
            $str,
6562
            $offset + (int) self::strlen($separator, $encoding),
6563
            null,
6564
            $encoding
6565
        );
6566
    }
6567
6568
    /**
6569
     * Gets the substring after the last occurrence of a separator.
6570
     *
6571
     * @param string $str       <p>The input string.</p>
6572
     * @param string $separator <p>The string separator.</p>
6573
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
6574
     *
6575
     * @psalm-pure
6576
     *
6577
     * @return string
6578
     */
6579 1
    public static function str_isubstr_after_last_separator(
6580
        string $str,
6581
        string $separator,
6582
        string $encoding = 'UTF-8'
6583
    ): string {
6584 1
        if ($separator === '' || $str === '') {
6585 1
            return '';
6586
        }
6587
6588 1
        $offset = self::strripos($str, $separator);
6589 1
        if ($offset === false) {
6590 1
            return '';
6591
        }
6592
6593 1
        if ($encoding === 'UTF-8') {
6594 1
            return (string) \mb_substr(
6595 1
                $str,
6596 1
                $offset + (int) self::strlen($separator)
6597
            );
6598
        }
6599
6600
        return (string) self::substr(
6601
            $str,
6602
            $offset + (int) self::strlen($separator, $encoding),
6603
            null,
6604
            $encoding
6605
        );
6606
    }
6607
6608
    /**
6609
     * Gets the substring before the first occurrence of a separator.
6610
     *
6611
     * @param string $str       <p>The input string.</p>
6612
     * @param string $separator <p>The string separator.</p>
6613
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
6614
     *
6615
     * @psalm-pure
6616
     *
6617
     * @return string
6618
     */
6619 1
    public static function str_isubstr_before_first_separator(
6620
        string $str,
6621
        string $separator,
6622
        string $encoding = 'UTF-8'
6623
    ): string {
6624 1
        if ($separator === '' || $str === '') {
6625 1
            return '';
6626
        }
6627
6628 1
        $offset = self::stripos($str, $separator);
6629 1
        if ($offset === false) {
6630 1
            return '';
6631
        }
6632
6633 1
        if ($encoding === 'UTF-8') {
6634 1
            return (string) \mb_substr($str, 0, $offset);
6635
        }
6636
6637
        return (string) self::substr($str, 0, $offset, $encoding);
6638
    }
6639
6640
    /**
6641
     * Gets the substring before the last occurrence of a separator.
6642
     *
6643
     * @param string $str       <p>The input string.</p>
6644
     * @param string $separator <p>The string separator.</p>
6645
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
6646
     *
6647
     * @psalm-pure
6648
     *
6649
     * @return string
6650
     */
6651 1
    public static function str_isubstr_before_last_separator(
6652
        string $str,
6653
        string $separator,
6654
        string $encoding = 'UTF-8'
6655
    ): string {
6656 1
        if ($separator === '' || $str === '') {
6657 1
            return '';
6658
        }
6659
6660 1
        if ($encoding === 'UTF-8') {
6661 1
            $offset = \mb_strripos($str, $separator);
6662 1
            if ($offset === false) {
6663 1
                return '';
6664
            }
6665
6666 1
            return (string) \mb_substr($str, 0, $offset);
6667
        }
6668
6669
        $offset = self::strripos($str, $separator, 0, $encoding);
6670
        if ($offset === false) {
6671
            return '';
6672
        }
6673
6674
        return (string) self::substr($str, 0, $offset, $encoding);
6675
    }
6676
6677
    /**
6678
     * Gets the substring after (or before via "$before_needle") the first occurrence of the "$needle".
6679
     *
6680
     * @param string $str           <p>The input string.</p>
6681
     * @param string $needle        <p>The string to look for.</p>
6682
     * @param bool   $before_needle [optional] <p>Default: false</p>
6683
     * @param string $encoding      [optional] <p>Default: 'UTF-8'</p>
6684
     *
6685
     * @psalm-pure
6686
     *
6687
     * @return string
6688
     */
6689 2
    public static function str_isubstr_first(
6690
        string $str,
6691
        string $needle,
6692
        bool $before_needle = false,
6693
        string $encoding = 'UTF-8'
6694
    ): string {
6695
        if (
6696 2
            $needle === ''
6697
            ||
6698 2
            $str === ''
6699
        ) {
6700 2
            return '';
6701
        }
6702
6703 2
        $part = self::stristr(
6704 2
            $str,
6705 2
            $needle,
6706 2
            $before_needle,
6707 2
            $encoding
6708
        );
6709 2
        if ($part === false) {
6710 2
            return '';
6711
        }
6712
6713 2
        return $part;
6714
    }
6715
6716
    /**
6717
     * Gets the substring after (or before via "$before_needle") the last occurrence of the "$needle".
6718
     *
6719
     * @param string $str           <p>The input string.</p>
6720
     * @param string $needle        <p>The string to look for.</p>
6721
     * @param bool   $before_needle [optional] <p>Default: false</p>
6722
     * @param string $encoding      [optional] <p>Default: 'UTF-8'</p>
6723
     *
6724
     * @psalm-pure
6725
     *
6726
     * @return string
6727
     */
6728 1
    public static function str_isubstr_last(
6729
        string $str,
6730
        string $needle,
6731
        bool $before_needle = false,
6732
        string $encoding = 'UTF-8'
6733
    ): string {
6734
        if (
6735 1
            $needle === ''
6736
            ||
6737 1
            $str === ''
6738
        ) {
6739 1
            return '';
6740
        }
6741
6742 1
        $part = self::strrichr(
6743 1
            $str,
6744 1
            $needle,
6745 1
            $before_needle,
6746 1
            $encoding
6747
        );
6748 1
        if ($part === false) {
6749 1
            return '';
6750
        }
6751
6752 1
        return $part;
6753
    }
6754
6755
    /**
6756
     * Returns the last $n characters of the string.
6757
     *
6758
     * @param string $str      <p>The input string.</p>
6759
     * @param int    $n        <p>Number of characters to retrieve from the end.</p>
6760
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6761
     *
6762
     * @psalm-pure
6763
     *
6764
     * @return string
6765
     */
6766 12
    public static function str_last_char(
6767
        string $str,
6768
        int $n = 1,
6769
        string $encoding = 'UTF-8'
6770
    ): string {
6771 12
        if ($str === '' || $n <= 0) {
6772 4
            return '';
6773
        }
6774
6775 8
        if ($encoding === 'UTF-8') {
6776 4
            return (string) \mb_substr($str, -$n);
6777
        }
6778
6779 4
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
6780
6781 4
        return (string) self::substr($str, -$n, null, $encoding);
6782
    }
6783
6784
    /**
6785
     * Limit the number of characters in a string.
6786
     *
6787
     * @param string $str        <p>The input string.</p>
6788
     * @param int    $length     [optional] <p>Default: 100</p>
6789
     * @param string $str_add_on [optional] <p>Default: …</p>
6790
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
6791
     *
6792
     * @psalm-pure
6793
     *
6794
     * @return string
6795
     */
6796 2
    public static function str_limit(
6797
        string $str,
6798
        int $length = 100,
6799
        string $str_add_on = '…',
6800
        string $encoding = 'UTF-8'
6801
    ): string {
6802 2
        if ($str === '' || $length <= 0) {
6803 2
            return '';
6804
        }
6805
6806 2
        if ($encoding === 'UTF-8') {
6807 2
            if ((int) \mb_strlen($str) <= $length) {
6808 2
                return $str;
6809
            }
6810
6811
            /** @noinspection UnnecessaryCastingInspection */
6812 2
            return (string) \mb_substr($str, 0, $length - (int) self::strlen($str_add_on)) . $str_add_on;
6813
        }
6814
6815
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
6816
6817
        if ((int) self::strlen($str, $encoding) <= $length) {
6818
            return $str;
6819
        }
6820
6821
        return ((string) self::substr($str, 0, $length - (int) self::strlen($str_add_on), $encoding)) . $str_add_on;
6822
    }
6823
6824
    /**
6825
     * Limit the number of characters in a string, but also after the next word.
6826
     *
6827
     * @param string $str        <p>The input string.</p>
6828
     * @param int    $length     [optional] <p>Default: 100</p>
6829
     * @param string $str_add_on [optional] <p>Default: …</p>
6830
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
6831
     *
6832
     * @psalm-pure
6833
     *
6834
     * @return string
6835
     */
6836 6
    public static function str_limit_after_word(
6837
        string $str,
6838
        int $length = 100,
6839
        string $str_add_on = '…',
6840
        string $encoding = 'UTF-8'
6841
    ): string {
6842 6
        if ($str === '' || $length <= 0) {
6843 2
            return '';
6844
        }
6845
6846 6
        if ($encoding === 'UTF-8') {
6847
            /** @noinspection UnnecessaryCastingInspection */
6848 2
            if ((int) \mb_strlen($str) <= $length) {
6849 2
                return $str;
6850
            }
6851
6852 2
            if (\mb_substr($str, $length - 1, 1) === ' ') {
6853 2
                return ((string) \mb_substr($str, 0, $length - 1)) . $str_add_on;
6854
            }
6855
6856 2
            $str = \mb_substr($str, 0, $length);
6857
6858 2
            $array = \explode(' ', $str, -1);
6859 2
            $new_str = \implode(' ', $array);
6860
6861 2
            if ($new_str === '') {
6862 2
                return ((string) \mb_substr($str, 0, $length - 1)) . $str_add_on;
6863
            }
6864
        } else {
6865 4
            if ((int) self::strlen($str, $encoding) <= $length) {
6866
                return $str;
6867
            }
6868
6869 4
            if (self::substr($str, $length - 1, 1, $encoding) === ' ') {
6870 3
                return ((string) self::substr($str, 0, $length - 1, $encoding)) . $str_add_on;
6871
            }
6872
6873
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
6874 1
            $str = self::substr($str, 0, $length, $encoding);
6875
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
6876 1
            if ($str === false) {
6877
                return '' . $str_add_on;
6878
            }
6879
6880 1
            $array = \explode(' ', $str, -1);
6881 1
            $new_str = \implode(' ', $array);
6882
6883 1
            if ($new_str === '') {
6884
                return ((string) self::substr($str, 0, $length - 1, $encoding)) . $str_add_on;
6885
            }
6886
        }
6887
6888 3
        return $new_str . $str_add_on;
6889
    }
6890
6891
    /**
6892
     * Returns the longest common prefix between the $str1 and $str2.
6893
     *
6894
     * @param string $str1     <p>The input sting.</p>
6895
     * @param string $str2     <p>Second string for comparison.</p>
6896
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6897
     *
6898
     * @psalm-pure
6899
     *
6900
     * @return string
6901
     */
6902 10
    public static function str_longest_common_prefix(
6903
        string $str1,
6904
        string $str2,
6905
        string $encoding = 'UTF-8'
6906
    ): string {
6907
        // init
6908 10
        $longest_common_prefix = '';
6909
6910 10
        if ($encoding === 'UTF-8') {
6911 5
            $max_length = (int) \min(
6912 5
                \mb_strlen($str1),
6913 5
                \mb_strlen($str2)
6914
            );
6915
6916 5
            for ($i = 0; $i < $max_length; ++$i) {
6917 4
                $char = \mb_substr($str1, $i, 1);
6918
6919
                if (
6920 4
                    $char !== false
6921
                    &&
6922 4
                    $char === \mb_substr($str2, $i, 1)
6923
                ) {
6924 3
                    $longest_common_prefix .= $char;
6925
                } else {
6926 3
                    break;
6927
                }
6928
            }
6929
        } else {
6930 5
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
6931
6932 5
            $max_length = (int) \min(
6933 5
                self::strlen($str1, $encoding),
6934 5
                self::strlen($str2, $encoding)
6935
            );
6936
6937 5
            for ($i = 0; $i < $max_length; ++$i) {
6938 4
                $char = self::substr($str1, $i, 1, $encoding);
6939
6940
                if (
6941 4
                    $char !== false
6942
                    &&
6943 4
                    $char === self::substr($str2, $i, 1, $encoding)
6944
                ) {
6945 3
                    $longest_common_prefix .= $char;
6946
                } else {
6947 3
                    break;
6948
                }
6949
            }
6950
        }
6951
6952 10
        return $longest_common_prefix;
6953
    }
6954
6955
    /**
6956
     * Returns the longest common substring between the $str1 and $str2.
6957
     * In the case of ties, it returns that which occurs first.
6958
     *
6959
     * @param string $str1
6960
     * @param string $str2     <p>Second string for comparison.</p>
6961
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6962
     *
6963
     * @psalm-pure
6964
     *
6965
     * @return string
6966
     *                <p>A string with its $str being the longest common substring.</p>
6967
     */
6968 11
    public static function str_longest_common_substring(
6969
        string $str1,
6970
        string $str2,
6971
        string $encoding = 'UTF-8'
6972
    ): string {
6973 11
        if ($str1 === '' || $str2 === '') {
6974 2
            return '';
6975
        }
6976
6977
        // Uses dynamic programming to solve
6978
        // http://en.wikipedia.org/wiki/Longest_common_substring_problem
6979
6980 9
        if ($encoding === 'UTF-8') {
6981 4
            $str_length = (int) \mb_strlen($str1);
6982 4
            $other_length = (int) \mb_strlen($str2);
6983
        } else {
6984 5
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
6985
6986 5
            $str_length = (int) self::strlen($str1, $encoding);
6987 5
            $other_length = (int) self::strlen($str2, $encoding);
6988
        }
6989
6990
        // Return if either string is empty
6991 9
        if ($str_length === 0 || $other_length === 0) {
6992
            return '';
6993
        }
6994
6995 9
        $len = 0;
6996 9
        $end = 0;
6997 9
        $table = \array_fill(
6998 9
            0,
6999 9
            $str_length + 1,
7000 9
            \array_fill(0, $other_length + 1, 0)
7001
        );
7002
7003 9
        if ($encoding === 'UTF-8') {
7004 9
            for ($i = 1; $i <= $str_length; ++$i) {
7005 9
                for ($j = 1; $j <= $other_length; ++$j) {
7006 9
                    $str_char = \mb_substr($str1, $i - 1, 1);
7007 9
                    $other_char = \mb_substr($str2, $j - 1, 1);
7008
7009 9
                    if ($str_char === $other_char) {
7010 8
                        $table[$i][$j] = $table[$i - 1][$j - 1] + 1;
7011 8
                        if ($table[$i][$j] > $len) {
7012 8
                            $len = $table[$i][$j];
7013 8
                            $end = $i;
7014
                        }
7015
                    } else {
7016 9
                        $table[$i][$j] = 0;
7017
                    }
7018
                }
7019
            }
7020
        } else {
7021
            for ($i = 1; $i <= $str_length; ++$i) {
7022
                for ($j = 1; $j <= $other_length; ++$j) {
7023
                    $str_char = self::substr($str1, $i - 1, 1, $encoding);
7024
                    $other_char = self::substr($str2, $j - 1, 1, $encoding);
7025
7026
                    if ($str_char === $other_char) {
7027
                        $table[$i][$j] = $table[$i - 1][$j - 1] + 1;
7028
                        if ($table[$i][$j] > $len) {
7029
                            $len = $table[$i][$j];
7030
                            $end = $i;
7031
                        }
7032
                    } else {
7033
                        $table[$i][$j] = 0;
7034
                    }
7035
                }
7036
            }
7037
        }
7038
7039 9
        if ($encoding === 'UTF-8') {
7040 9
            return (string) \mb_substr($str1, $end - $len, $len);
7041
        }
7042
7043
        return (string) self::substr($str1, $end - $len, $len, $encoding);
7044
    }
7045
7046
    /**
7047
     * Returns the longest common suffix between the $str1 and $str2.
7048
     *
7049
     * @param string $str1
7050
     * @param string $str2     <p>Second string for comparison.</p>
7051
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7052
     *
7053
     * @psalm-pure
7054
     *
7055
     * @return string
7056
     */
7057 10
    public static function str_longest_common_suffix(
7058
        string $str1,
7059
        string $str2,
7060
        string $encoding = 'UTF-8'
7061
    ): string {
7062 10
        if ($str1 === '' || $str2 === '') {
7063 2
            return '';
7064
        }
7065
7066 8
        if ($encoding === 'UTF-8') {
7067 4
            $max_length = (int) \min(
7068 4
                \mb_strlen($str1, $encoding),
7069 4
                \mb_strlen($str2, $encoding)
7070
            );
7071
7072 4
            $longest_common_suffix = '';
7073 4
            for ($i = 1; $i <= $max_length; ++$i) {
7074 4
                $char = \mb_substr($str1, -$i, 1);
7075
7076
                if (
7077 4
                    $char !== false
7078
                    &&
7079 4
                    $char === \mb_substr($str2, -$i, 1)
7080
                ) {
7081 3
                    $longest_common_suffix = $char . $longest_common_suffix;
7082
                } else {
7083 3
                    break;
7084
                }
7085
            }
7086
        } else {
7087 4
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
7088
7089 4
            $max_length = (int) \min(
7090 4
                self::strlen($str1, $encoding),
7091 4
                self::strlen($str2, $encoding)
7092
            );
7093
7094 4
            $longest_common_suffix = '';
7095 4
            for ($i = 1; $i <= $max_length; ++$i) {
7096 4
                $char = self::substr($str1, -$i, 1, $encoding);
7097
7098
                if (
7099 4
                    $char !== false
7100
                    &&
7101 4
                    $char === self::substr($str2, -$i, 1, $encoding)
7102
                ) {
7103 3
                    $longest_common_suffix = $char . $longest_common_suffix;
7104
                } else {
7105 3
                    break;
7106
                }
7107
            }
7108
        }
7109
7110 8
        return $longest_common_suffix;
7111
    }
7112
7113
    /**
7114
     * Returns true if $str matches the supplied pattern, false otherwise.
7115
     *
7116
     * @param string $str     <p>The input string.</p>
7117
     * @param string $pattern <p>Regex pattern to match against.</p>
7118
     *
7119
     * @psalm-pure
7120
     *
7121
     * @return bool whether or not $str matches the pattern
7122
     */
7123 10
    public static function str_matches_pattern(string $str, string $pattern): bool
7124
    {
7125 10
        return (bool) \preg_match('/' . $pattern . '/u', $str);
7126
    }
7127
7128
    /**
7129
     * Returns whether or not a character exists at an index. Offsets may be
7130
     * negative to count from the last character in the string. Implements
7131
     * part of the ArrayAccess interface.
7132
     *
7133
     * @param string $str      <p>The input string.</p>
7134
     * @param int    $offset   <p>The index to check.</p>
7135
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7136
     *
7137
     * @psalm-pure
7138
     *
7139
     * @return bool whether or not the index exists
7140
     */
7141 6
    public static function str_offset_exists(string $str, int $offset, string $encoding = 'UTF-8'): bool
7142
    {
7143
        // init
7144 6
        $length = (int) self::strlen($str, $encoding);
7145
7146 6
        if ($offset >= 0) {
7147 3
            return $length > $offset;
7148
        }
7149
7150 3
        return $length >= \abs($offset);
7151
    }
7152
7153
    /**
7154
     * Returns the character at the given index. Offsets may be negative to
7155
     * count from the last character in the string. Implements part of the
7156
     * ArrayAccess interface, and throws an OutOfBoundsException if the index
7157
     * does not exist.
7158
     *
7159
     * @param string $str      <p>The input string.</p>
7160
     * @param int    $index    <p>The <strong>index</strong> from which to retrieve the char.</p>
7161
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7162
     *
7163
     * @throws \OutOfBoundsException if the positive or negative offset does not exist
7164
     *
7165
     * @return string
7166
     *                <p>The character at the specified index.</p>
7167
     *
7168
     * @psalm-pure
7169
     */
7170 2
    public static function str_offset_get(string $str, int $index, string $encoding = 'UTF-8'): string
7171
    {
7172
        // init
7173 2
        $length = (int) self::strlen($str);
7174
7175
        if (
7176 2
            ($index >= 0 && $length <= $index)
7177
            ||
7178 2
            $length < \abs($index)
7179
        ) {
7180 1
            throw new \OutOfBoundsException('No character exists at the index');
7181
        }
7182
7183 1
        return self::char_at($str, $index, $encoding);
7184
    }
7185
7186
    /**
7187
     * Pad a UTF-8 string to a given length with another string.
7188
     *
7189
     * @param string     $str        <p>The input string.</p>
7190
     * @param int        $pad_length <p>The length of return string.</p>
7191
     * @param string     $pad_string [optional] <p>String to use for padding the input string.</p>
7192
     * @param int|string $pad_type   [optional] <p>
7193
     *                               Can be <strong>STR_PAD_RIGHT</strong> (default), [or string "right"]<br>
7194
     *                               <strong>STR_PAD_LEFT</strong> [or string "left"] or<br>
7195
     *                               <strong>STR_PAD_BOTH</strong> [or string "both"]
7196
     *                               </p>
7197
     * @param string     $encoding   [optional] <p>Default: 'UTF-8'</p>
7198
     *
7199
     * @psalm-pure
7200
     *
7201
     * @return string
7202
     *                <p>Returns the padded string.</p>
7203
     */
7204 41
    public static function str_pad(
7205
        string $str,
7206
        int $pad_length,
7207
        string $pad_string = ' ',
7208
        $pad_type = \STR_PAD_RIGHT,
7209
        string $encoding = 'UTF-8'
7210
    ): string {
7211 41
        if ($pad_length === 0 || $pad_string === '') {
7212 1
            return $str;
7213
        }
7214
7215 41
        if ($pad_type !== (int) $pad_type) {
7216 13
            if ($pad_type === 'left') {
7217 3
                $pad_type = \STR_PAD_LEFT;
7218 10
            } elseif ($pad_type === 'right') {
7219 6
                $pad_type = \STR_PAD_RIGHT;
7220 4
            } elseif ($pad_type === 'both') {
7221 3
                $pad_type = \STR_PAD_BOTH;
7222
            } else {
7223 1
                throw new \InvalidArgumentException(
7224 1
                    'Pad expects $pad_type to be "STR_PAD_*" or ' . "to be one of 'left', 'right' or 'both'"
7225
                );
7226
            }
7227
        }
7228
7229 40
        if ($encoding === 'UTF-8') {
7230 25
            $str_length = (int) \mb_strlen($str);
7231
7232 25
            if ($pad_length >= $str_length) {
7233
                switch ($pad_type) {
7234 25
                    case \STR_PAD_LEFT:
7235 8
                        $ps_length = (int) \mb_strlen($pad_string);
7236
7237 8
                        $diff = ($pad_length - $str_length);
7238
7239 8
                        $pre = (string) \mb_substr(
7240 8
                            \str_repeat($pad_string, (int) \ceil($diff / $ps_length)),
7241 8
                            0,
7242 8
                            $diff
7243
                        );
7244 8
                        $post = '';
7245
7246 8
                        break;
7247
7248 20
                    case \STR_PAD_BOTH:
7249 14
                        $diff = ($pad_length - $str_length);
7250
7251 14
                        $ps_length_left = (int) \floor($diff / 2);
7252
7253 14
                        $ps_length_right = (int) \ceil($diff / 2);
7254
7255 14
                        $pre = (string) \mb_substr(
7256 14
                            \str_repeat($pad_string, $ps_length_left),
7257 14
                            0,
7258 14
                            $ps_length_left
7259
                        );
7260 14
                        $post = (string) \mb_substr(
7261 14
                            \str_repeat($pad_string, $ps_length_right),
7262 14
                            0,
7263 14
                            $ps_length_right
7264
                        );
7265
7266 14
                        break;
7267
7268 9
                    case \STR_PAD_RIGHT:
7269
                    default:
7270 9
                        $ps_length = (int) \mb_strlen($pad_string);
7271
7272 9
                        $diff = ($pad_length - $str_length);
7273
7274 9
                        $post = (string) \mb_substr(
7275 9
                            \str_repeat($pad_string, (int) \ceil($diff / $ps_length)),
7276 9
                            0,
7277 9
                            $diff
7278
                        );
7279 9
                        $pre = '';
7280
                }
7281
7282 25
                return $pre . $str . $post;
7283
            }
7284
7285 3
            return $str;
7286
        }
7287
7288 15
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
7289
7290 15
        $str_length = (int) self::strlen($str, $encoding);
7291
7292 15
        if ($pad_length >= $str_length) {
7293
            switch ($pad_type) {
7294 14
                case \STR_PAD_LEFT:
7295 5
                    $ps_length = (int) self::strlen($pad_string, $encoding);
7296
7297 5
                    $diff = ($pad_length - $str_length);
7298
7299 5
                    $pre = (string) self::substr(
7300 5
                        \str_repeat($pad_string, (int) \ceil($diff / $ps_length)),
7301 5
                        0,
7302 5
                        $diff,
7303 5
                        $encoding
7304
                    );
7305 5
                    $post = '';
7306
7307 5
                    break;
7308
7309 9
                case \STR_PAD_BOTH:
7310 3
                    $diff = ($pad_length - $str_length);
7311
7312 3
                    $ps_length_left = (int) \floor($diff / 2);
7313
7314 3
                    $ps_length_right = (int) \ceil($diff / 2);
7315
7316 3
                    $pre = (string) self::substr(
7317 3
                        \str_repeat($pad_string, $ps_length_left),
7318 3
                        0,
7319 3
                        $ps_length_left,
7320 3
                        $encoding
7321
                    );
7322 3
                    $post = (string) self::substr(
7323 3
                        \str_repeat($pad_string, $ps_length_right),
7324 3
                        0,
7325 3
                        $ps_length_right,
7326 3
                        $encoding
7327
                    );
7328
7329 3
                    break;
7330
7331 6
                case \STR_PAD_RIGHT:
7332
                default:
7333 6
                    $ps_length = (int) self::strlen($pad_string, $encoding);
7334
7335 6
                    $diff = ($pad_length - $str_length);
7336
7337 6
                    $post = (string) self::substr(
7338 6
                        \str_repeat($pad_string, (int) \ceil($diff / $ps_length)),
7339 6
                        0,
7340 6
                        $diff,
7341 6
                        $encoding
7342
                    );
7343 6
                    $pre = '';
7344
            }
7345
7346 14
            return $pre . $str . $post;
7347
        }
7348
7349 1
        return $str;
7350
    }
7351
7352
    /**
7353
     * Returns a new string of a given length such that both sides of the
7354
     * string are padded. Alias for "UTF8::str_pad()" with a $pad_type of 'both'.
7355
     *
7356
     * @param string $str
7357
     * @param int    $length   <p>Desired string length after padding.</p>
7358
     * @param string $pad_str  [optional] <p>String used to pad, defaults to space. Default: ' '</p>
7359
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7360
     *
7361
     * @psalm-pure
7362
     *
7363
     * @return string
7364
     *                <p>The string with padding applied.</p>
7365
     */
7366 11
    public static function str_pad_both(
7367
        string $str,
7368
        int $length,
7369
        string $pad_str = ' ',
7370
        string $encoding = 'UTF-8'
7371
    ): string {
7372 11
        return self::str_pad(
7373 11
            $str,
7374 11
            $length,
7375 11
            $pad_str,
7376 11
            \STR_PAD_BOTH,
7377 11
            $encoding
7378
        );
7379
    }
7380
7381
    /**
7382
     * Returns a new string of a given length such that the beginning of the
7383
     * string is padded. Alias for "UTF8::str_pad()" with a $pad_type of 'left'.
7384
     *
7385
     * @param string $str
7386
     * @param int    $length   <p>Desired string length after padding.</p>
7387
     * @param string $pad_str  [optional] <p>String used to pad, defaults to space. Default: ' '</p>
7388
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7389
     *
7390
     * @psalm-pure
7391
     *
7392
     * @return string
7393
     *                <p>The string with left padding.</p>
7394
     */
7395 7
    public static function str_pad_left(
7396
        string $str,
7397
        int $length,
7398
        string $pad_str = ' ',
7399
        string $encoding = 'UTF-8'
7400
    ): string {
7401 7
        return self::str_pad(
7402 7
            $str,
7403 7
            $length,
7404 7
            $pad_str,
7405 7
            \STR_PAD_LEFT,
7406 7
            $encoding
7407
        );
7408
    }
7409
7410
    /**
7411
     * Returns a new string of a given length such that the end of the string
7412
     * is padded. Alias for "UTF8::str_pad()" with a $pad_type of 'right'.
7413
     *
7414
     * @param string $str
7415
     * @param int    $length   <p>Desired string length after padding.</p>
7416
     * @param string $pad_str  [optional] <p>String used to pad, defaults to space. Default: ' '</p>
7417
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7418
     *
7419
     * @psalm-pure
7420
     *
7421
     * @return string
7422
     *                <p>The string with right padding.</p>
7423
     */
7424 7
    public static function str_pad_right(
7425
        string $str,
7426
        int $length,
7427
        string $pad_str = ' ',
7428
        string $encoding = 'UTF-8'
7429
    ): string {
7430 7
        return self::str_pad(
7431 7
            $str,
7432 7
            $length,
7433 7
            $pad_str,
7434 7
            \STR_PAD_RIGHT,
7435 7
            $encoding
7436
        );
7437
    }
7438
7439
    /**
7440
     * Repeat a string.
7441
     *
7442
     * @param string $str        <p>
7443
     *                           The string to be repeated.
7444
     *                           </p>
7445
     * @param int    $multiplier <p>
7446
     *                           Number of time the input string should be
7447
     *                           repeated.
7448
     *                           </p>
7449
     *                           <p>
7450
     *                           multiplier has to be greater than or equal to 0.
7451
     *                           If the multiplier is set to 0, the function
7452
     *                           will return an empty string.
7453
     *                           </p>
7454
     *
7455
     * @psalm-pure
7456
     *
7457
     * @return string
7458
     *                <p>The repeated string.</p>
7459
     */
7460 9
    public static function str_repeat(string $str, int $multiplier): string
7461
    {
7462 9
        $str = self::filter($str);
7463
7464 9
        return \str_repeat($str, $multiplier);
7465
    }
7466
7467
    /**
7468
     * INFO: This is only a wrapper for "str_replace()"  -> the original functions is already UTF-8 safe.
7469
     *
7470
     * Replace all occurrences of the search string with the replacement string
7471
     *
7472
     * @see http://php.net/manual/en/function.str-replace.php
7473
     *
7474
     * @param mixed $search  <p>
7475
     *                       The value being searched for, otherwise known as the needle.
7476
     *                       An array may be used to designate multiple needles.
7477
     *                       </p>
7478
     * @param mixed $replace <p>
7479
     *                       The replacement value that replaces found search
7480
     *                       values. An array may be used to designate multiple replacements.
7481
     *                       </p>
7482
     * @param mixed $subject <p>
7483
     *                       The string or array being searched and replaced on,
7484
     *                       otherwise known as the haystack.
7485
     *                       </p>
7486
     *                       <p>
7487
     *                       If subject is an array, then the search and
7488
     *                       replace is performed with every entry of
7489
     *                       subject, and the return value is an array as
7490
     *                       well.
7491
     *                       </p>
7492
     * @param int   $count   [optional] If passed, this will hold the number of matched and replaced needles
7493
     *
7494
     * @psalm-pure
7495
     *
7496
     * @return mixed this function returns a string or an array with the replaced values
7497
     */
7498 12
    public static function str_replace(
7499
        $search,
7500
        $replace,
7501
        $subject,
7502
        int &$count = null
7503
    ) {
7504
        /**
7505
         * @psalm-suppress PossiblyNullArgument
7506
         */
7507 12
        return \str_replace(
7508 12
            $search,
7509 12
            $replace,
7510 12
            $subject,
7511 12
            $count
7512
        );
7513
    }
7514
7515
    /**
7516
     * Replaces $search from the beginning of string with $replacement.
7517
     *
7518
     * @param string $str         <p>The input string.</p>
7519
     * @param string $search      <p>The string to search for.</p>
7520
     * @param string $replacement <p>The replacement.</p>
7521
     *
7522
     * @psalm-pure
7523
     *
7524
     * @return string
7525
     *                <p>A string after the replacements.</p>
7526
     */
7527 17
    public static function str_replace_beginning(
7528
        string $str,
7529
        string $search,
7530
        string $replacement
7531
    ): string {
7532 17
        if ($str === '') {
7533 4
            if ($replacement === '') {
7534 2
                return '';
7535
            }
7536
7537 2
            if ($search === '') {
7538 2
                return $replacement;
7539
            }
7540
        }
7541
7542 13
        if ($search === '') {
7543 2
            return $str . $replacement;
7544
        }
7545
7546 11
        if (\strpos($str, $search) === 0) {
7547 9
            return $replacement . \substr($str, \strlen($search));
7548
        }
7549
7550 2
        return $str;
7551
    }
7552
7553
    /**
7554
     * Replaces $search from the ending of string with $replacement.
7555
     *
7556
     * @param string $str         <p>The input string.</p>
7557
     * @param string $search      <p>The string to search for.</p>
7558
     * @param string $replacement <p>The replacement.</p>
7559
     *
7560
     * @psalm-pure
7561
     *
7562
     * @return string
7563
     *                <p>A string after the replacements.</p>
7564
     */
7565 17
    public static function str_replace_ending(
7566
        string $str,
7567
        string $search,
7568
        string $replacement
7569
    ): string {
7570 17
        if ($str === '') {
7571 4
            if ($replacement === '') {
7572 2
                return '';
7573
            }
7574
7575 2
            if ($search === '') {
7576 2
                return $replacement;
7577
            }
7578
        }
7579
7580 13
        if ($search === '') {
7581 2
            return $str . $replacement;
7582
        }
7583
7584 11
        if (\strpos($str, $search, \strlen($str) - \strlen($search)) !== false) {
7585 8
            $str = \substr($str, 0, -\strlen($search)) . $replacement;
7586
        }
7587
7588 11
        return $str;
7589
    }
7590
7591
    /**
7592
     * Replace the first "$search"-term with the "$replace"-term.
7593
     *
7594
     * @param string $search
7595
     * @param string $replace
7596
     * @param string $subject
7597
     *
7598
     * @psalm-pure
7599
     *
7600
     * @return string
7601
     *
7602
     * @psalm-suppress InvalidReturnType
7603
     */
7604 2
    public static function str_replace_first(
7605
        string $search,
7606
        string $replace,
7607
        string $subject
7608
    ): string {
7609 2
        $pos = self::strpos($subject, $search);
7610
7611 2
        if ($pos !== false) {
7612
            /**
7613
             * @psalm-suppress InvalidReturnStatement
7614
             */
7615 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...
7616 2
                $subject,
7617 2
                $replace,
7618 2
                $pos,
7619 2
                (int) self::strlen($search)
7620
            );
7621
        }
7622
7623 2
        return $subject;
7624
    }
7625
7626
    /**
7627
     * Replace the last "$search"-term with the "$replace"-term.
7628
     *
7629
     * @param string $search
7630
     * @param string $replace
7631
     * @param string $subject
7632
     *
7633
     * @psalm-pure
7634
     *
7635
     * @return string
7636
     *
7637
     * @psalm-suppress InvalidReturnType
7638
     */
7639 2
    public static function str_replace_last(
7640
        string $search,
7641
        string $replace,
7642
        string $subject
7643
    ): string {
7644 2
        $pos = self::strrpos($subject, $search);
7645 2
        if ($pos !== false) {
7646
            /**
7647
             * @psalm-suppress InvalidReturnStatement
7648
             */
7649 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...
7650 2
                $subject,
7651 2
                $replace,
7652 2
                $pos,
7653 2
                (int) self::strlen($search)
7654
            );
7655
        }
7656
7657 2
        return $subject;
7658
    }
7659
7660
    /**
7661
     * Shuffles all the characters in the string.
7662
     *
7663
     * PS: uses random algorithm which is weak for cryptography purposes
7664
     *
7665
     * @param string $str      <p>The input string</p>
7666
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7667
     *
7668
     * @return string
7669
     *                <p>The shuffled string.</p>
7670
     */
7671 5
    public static function str_shuffle(string $str, string $encoding = 'UTF-8'): string
7672
    {
7673 5
        if ($encoding === 'UTF-8') {
7674 5
            $indexes = \range(0, (int) \mb_strlen($str) - 1);
7675
            /** @noinspection NonSecureShuffleUsageInspection */
7676 5
            \shuffle($indexes);
7677
7678
            // init
7679 5
            $shuffled_str = '';
7680
7681 5
            foreach ($indexes as &$i) {
7682 5
                $tmp_sub_str = \mb_substr($str, $i, 1);
7683 5
                if ($tmp_sub_str !== false) {
7684 5
                    $shuffled_str .= $tmp_sub_str;
7685
                }
7686
            }
7687
        } else {
7688
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
7689
7690
            $indexes = \range(0, (int) self::strlen($str, $encoding) - 1);
7691
            /** @noinspection NonSecureShuffleUsageInspection */
7692
            \shuffle($indexes);
7693
7694
            // init
7695
            $shuffled_str = '';
7696
7697
            foreach ($indexes as &$i) {
7698
                $tmp_sub_str = self::substr($str, $i, 1, $encoding);
7699
                if ($tmp_sub_str !== false) {
7700
                    $shuffled_str .= $tmp_sub_str;
7701
                }
7702
            }
7703
        }
7704
7705 5
        return $shuffled_str;
7706
    }
7707
7708
    /**
7709
     * Returns the substring beginning at $start, and up to, but not including
7710
     * the index specified by $end. If $end is omitted, the function extracts
7711
     * the remaining string. If $end is negative, it is computed from the end
7712
     * of the string.
7713
     *
7714
     * @param string $str
7715
     * @param int    $start    <p>Initial index from which to begin extraction.</p>
7716
     * @param int    $end      [optional] <p>Index at which to end extraction. Default: null</p>
7717
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7718
     *
7719
     * @psalm-pure
7720
     *
7721
     * @return false|string
7722
     *                      <p>The extracted substring.</p><p>If <i>str</i> is shorter than <i>start</i>
7723
     *                      characters long, <b>FALSE</b> will be returned.
7724
     */
7725 18
    public static function str_slice(
7726
        string $str,
7727
        int $start,
7728
        int $end = null,
7729
        string $encoding = 'UTF-8'
7730
    ) {
7731 18
        if ($encoding === 'UTF-8') {
7732 7
            if ($end === null) {
7733 1
                $length = (int) \mb_strlen($str);
7734 6
            } elseif ($end >= 0 && $end <= $start) {
7735 2
                return '';
7736 4
            } elseif ($end < 0) {
7737 1
                $length = (int) \mb_strlen($str) + $end - $start;
7738
            } else {
7739 3
                $length = $end - $start;
7740
            }
7741
7742 5
            return \mb_substr($str, $start, $length);
7743
        }
7744
7745 11
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
7746
7747 11
        if ($end === null) {
7748 5
            $length = (int) self::strlen($str, $encoding);
7749 6
        } elseif ($end >= 0 && $end <= $start) {
7750 2
            return '';
7751 4
        } elseif ($end < 0) {
7752 1
            $length = (int) self::strlen($str, $encoding) + $end - $start;
7753
        } else {
7754 3
            $length = $end - $start;
7755
        }
7756
7757 9
        return self::substr($str, $start, $length, $encoding);
7758
    }
7759
7760
    /**
7761
     * Convert a string to e.g.: "snake_case"
7762
     *
7763
     * @param string $str
7764
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7765
     *
7766
     * @psalm-pure
7767
     *
7768
     * @return string
7769
     *                <p>A string in snake_case.</p>
7770
     */
7771 22
    public static function str_snakeize(string $str, string $encoding = 'UTF-8'): string
7772
    {
7773 22
        if ($str === '') {
7774
            return '';
7775
        }
7776
7777 22
        $str = \str_replace(
7778 22
            '-',
7779 22
            '_',
7780 22
            self::normalize_whitespace($str)
7781
        );
7782
7783 22
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
7784 19
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
7785
        }
7786
7787 22
        $str = (string) \preg_replace_callback(
7788 22
            '/([\\p{N}|\\p{Lu}])/u',
7789
            /**
7790
             * @param string[] $matches
7791
             *
7792
             * @psalm-pure
7793
             *
7794
             * @return string
7795
             */
7796
            static function (array $matches) use ($encoding): string {
7797 9
                $match = $matches[1];
7798 9
                $match_int = (int) $match;
7799
7800 9
                if ((string) $match_int === $match) {
7801 4
                    return '_' . $match . '_';
7802
                }
7803
7804 5
                if ($encoding === 'UTF-8') {
7805 5
                    return '_' . \mb_strtolower($match);
7806
                }
7807
7808
                return '_' . self::strtolower($match, $encoding);
7809 22
            },
7810 22
            $str
7811
        );
7812
7813 22
        $str = (string) \preg_replace(
7814
            [
7815 22
                '/\\s+/u',           // convert spaces to "_"
7816
                '/^\\s+|\\s+$/u', // trim leading & trailing spaces
7817
                '/_+/',                 // remove double "_"
7818
            ],
7819
            [
7820 22
                '_',
7821
                '',
7822
                '_',
7823
            ],
7824 22
            $str
7825
        );
7826
7827 22
        return \trim(\trim($str, '_')); // trim leading & trailing "_" + whitespace
7828
    }
7829
7830
    /**
7831
     * Sort all characters according to code points.
7832
     *
7833
     * @param string $str    <p>A UTF-8 string.</p>
7834
     * @param bool   $unique <p>Sort unique. If <strong>true</strong>, repeated characters are ignored.</p>
7835
     * @param bool   $desc   <p>If <strong>true</strong>, will sort characters in reverse code point order.</p>
7836
     *
7837
     * @psalm-pure
7838
     *
7839
     * @return string
7840
     *                <p>A string of sorted characters.</p>
7841
     */
7842 2
    public static function str_sort(string $str, bool $unique = false, bool $desc = false): string
7843
    {
7844 2
        $array = self::codepoints($str);
7845
7846 2
        if ($unique) {
7847 2
            $array = \array_flip(\array_flip($array));
7848
        }
7849
7850 2
        if ($desc) {
7851 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

7851
            \arsort(/** @scrutinizer ignore-type */ $array);
Loading history...
7852
        } else {
7853 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

7853
            \asort(/** @scrutinizer ignore-type */ $array);
Loading history...
7854
        }
7855
7856 2
        return self::string($array);
7857
    }
7858
7859
    /**
7860
     * Convert a string to an array of Unicode characters.
7861
     *
7862
     * @param int[]|string[] $input                   <p>The string[] or int[] to split into array.</p>
7863
     * @param int            $length                  [optional] <p>Max character length of each array
7864
     *                                                lement.</p>
7865
     * @param bool           $clean_utf8              [optional] <p>Remove non UTF-8 chars from the
7866
     *                                                string.</p>
7867
     * @param bool           $try_to_use_mb_functions [optional] <p>Set to false, if you don't want to use
7868
     *                                                "mb_substr"</p>
7869
     *
7870
     * @psalm-pure
7871
     *
7872
     * @return string[][]
7873
     *                    <p>An array containing chunks of the input.</p>
7874
     */
7875 1
    public static function str_split_array(
7876
        array $input,
7877
        int $length = 1,
7878
        bool $clean_utf8 = false,
7879
        bool $try_to_use_mb_functions = true
7880
    ): array {
7881 1
        foreach ($input as $k => &$v) {
7882 1
            $v = self::str_split(
7883 1
                $v,
7884 1
                $length,
7885 1
                $clean_utf8,
7886 1
                $try_to_use_mb_functions
7887
            );
7888
        }
7889
7890
        /** @var string[][] $input */
7891 1
        return $input;
7892
    }
7893
7894
    /**
7895
     * Convert a string to an array of unicode characters.
7896
     *
7897
     * @param int|int[]|string|string[] $input                   <p>The string or int to split into array.</p>
7898
     * @param int                       $length                  [optional] <p>Max character length of each array
7899
     *                                                           element.</p>
7900
     * @param bool                      $clean_utf8              [optional] <p>Remove non UTF-8 chars from the
7901
     *                                                           string.</p>
7902
     * @param bool                      $try_to_use_mb_functions [optional] <p>Set to false, if you don't want to use
7903
     *                                                           "mb_substr"</p>
7904
     *
7905
     * @psalm-pure
7906
     *
7907
     * @return string[]|string[][]
7908
     *                             <p>An array containing chunks of chars from the input.</p>
7909
     *
7910
     * @noinspection SuspiciousBinaryOperationInspection
7911
     */
7912 89
    public static function str_split(
7913
        $input,
7914
        int $length = 1,
7915
        bool $clean_utf8 = false,
7916
        bool $try_to_use_mb_functions = true
7917
    ): array {
7918 89
        if ($length <= 0) {
7919 3
            return [];
7920
        }
7921
7922
        // this is only an old fallback
7923
        /**
7924
         * @psalm-suppress DocblockTypeContradiction
7925
         */
7926 88
        if (\is_array($input)) {
7927
            /**
7928
             * @psalm-suppress InvalidReturnStatement
7929
             */
7930
            return self::str_split_array(
7931
                $input,
7932
                $length,
7933
                $clean_utf8,
7934
                $try_to_use_mb_functions
7935
            );
7936
        }
7937
7938
        // init
7939 88
        $input = (string) $input;
7940
7941 88
        if ($input === '') {
7942 13
            return [];
7943
        }
7944
7945 85
        if ($clean_utf8) {
7946 19
            $input = self::clean($input);
7947
        }
7948
7949
        if (
7950 85
            $try_to_use_mb_functions
7951
            &&
7952 85
            self::$SUPPORT['mbstring'] === true
7953
        ) {
7954 81
            if (Bootup::is_php('7.4')) {
7955
                /**
7956
                 * @psalm-suppress ImpureFunctionCall - why?
7957
                 */
7958
                $return = \mb_str_split($input, $length);
7959
                if ($return !== false) {
7960
                    return $return;
7961
                }
7962
            }
7963
7964 81
            $i_max = \mb_strlen($input);
7965 81
            if ($i_max <= 127) {
7966 75
                $ret = [];
7967 75
                for ($i = 0; $i < $i_max; ++$i) {
7968 75
                    $ret[] = \mb_substr($input, $i, 1);
7969
                }
7970
            } else {
7971 16
                $return_array = [];
7972 16
                \preg_match_all('/./us', $input, $return_array);
7973 81
                $ret = $return_array[0] ?? [];
7974
            }
7975 23
        } elseif (self::$SUPPORT['pcre_utf8'] === true) {
7976 17
            $return_array = [];
7977 17
            \preg_match_all('/./us', $input, $return_array);
7978 17
            $ret = $return_array[0] ?? [];
7979
        } else {
7980
7981
            // fallback
7982
7983 8
            $ret = [];
7984 8
            $len = \strlen($input);
7985
7986
            /** @noinspection ForeachInvariantsInspection */
7987 8
            for ($i = 0; $i < $len; ++$i) {
7988 8
                if (($input[$i] & "\x80") === "\x00") {
7989 8
                    $ret[] = $input[$i];
7990
                } elseif (
7991 8
                    isset($input[$i + 1])
7992
                    &&
7993 8
                    ($input[$i] & "\xE0") === "\xC0"
7994
                ) {
7995 4
                    if (($input[$i + 1] & "\xC0") === "\x80") {
7996 4
                        $ret[] = $input[$i] . $input[$i + 1];
7997
7998 4
                        ++$i;
7999
                    }
8000
                } elseif (
8001 6
                    isset($input[$i + 2])
8002
                    &&
8003 6
                    ($input[$i] & "\xF0") === "\xE0"
8004
                ) {
8005
                    if (
8006 6
                        ($input[$i + 1] & "\xC0") === "\x80"
8007
                        &&
8008 6
                        ($input[$i + 2] & "\xC0") === "\x80"
8009
                    ) {
8010 6
                        $ret[] = $input[$i] . $input[$i + 1] . $input[$i + 2];
8011
8012 6
                        $i += 2;
8013
                    }
8014
                } elseif (
8015
                    isset($input[$i + 3])
8016
                    &&
8017
                    ($input[$i] & "\xF8") === "\xF0"
8018
                ) {
8019
                    if (
8020
                        ($input[$i + 1] & "\xC0") === "\x80"
8021
                        &&
8022
                        ($input[$i + 2] & "\xC0") === "\x80"
8023
                        &&
8024
                        ($input[$i + 3] & "\xC0") === "\x80"
8025
                    ) {
8026
                        $ret[] = $input[$i] . $input[$i + 1] . $input[$i + 2] . $input[$i + 3];
8027
8028
                        $i += 3;
8029
                    }
8030
                }
8031
            }
8032
        }
8033
8034 85
        if ($length > 1) {
8035 11
            $ret = \array_chunk($ret, $length);
8036
8037 11
            return \array_map(
8038
                static function (array &$item): string {
8039 11
                    return \implode('', $item);
8040 11
                },
8041 11
                $ret
8042
            );
8043
        }
8044
8045 78
        if (isset($ret[0]) && $ret[0] === '') {
8046
            return [];
8047
        }
8048
8049 78
        return $ret;
8050
    }
8051
8052
    /**
8053
     * Splits the string with the provided regular expression, returning an
8054
     * array of strings. An optional integer $limit will truncate the
8055
     * results.
8056
     *
8057
     * @param string $str
8058
     * @param string $pattern <p>The regex with which to split the string.</p>
8059
     * @param int    $limit   [optional] <p>Maximum number of results to return. Default: -1 === no limit</p>
8060
     *
8061
     * @psalm-pure
8062
     *
8063
     * @return string[]
8064
     *                  <p>An array of strings.</p>
8065
     */
8066 16
    public static function str_split_pattern(string $str, string $pattern, int $limit = -1): array
8067
    {
8068 16
        if ($limit === 0) {
8069 2
            return [];
8070
        }
8071
8072 14
        if ($pattern === '') {
8073 1
            return [$str];
8074
        }
8075
8076 13
        if (self::$SUPPORT['mbstring'] === true) {
8077 13
            if ($limit >= 0) {
8078
                /** @noinspection PhpComposerExtensionStubsInspection */
8079 8
                $result_tmp = \mb_split($pattern, $str);
8080
8081 8
                $result = [];
8082 8
                foreach ($result_tmp as $item_tmp) {
8083 8
                    if ($limit === 0) {
8084 4
                        break;
8085
                    }
8086 8
                    --$limit;
8087
8088 8
                    $result[] = $item_tmp;
8089
                }
8090
8091 8
                return $result;
8092
            }
8093
8094
            /** @noinspection PhpComposerExtensionStubsInspection */
8095 5
            return \mb_split($pattern, $str);
8096
        }
8097
8098
        if ($limit > 0) {
8099
            ++$limit;
8100
        } else {
8101
            $limit = -1;
8102
        }
8103
8104
        $array = \preg_split('/' . \preg_quote($pattern, '/') . '/u', $str, $limit);
8105
8106
        if ($array === false) {
8107
            return [];
8108
        }
8109
8110
        if ($limit > 0 && \count($array) === $limit) {
8111
            \array_pop($array);
8112
        }
8113
8114
        return $array;
8115
    }
8116
8117
    /**
8118
     * Check if the string starts with the given substring.
8119
     *
8120
     * @param string $haystack <p>The string to search in.</p>
8121
     * @param string $needle   <p>The substring to search for.</p>
8122
     *
8123
     * @psalm-pure
8124
     *
8125
     * @return bool
8126
     */
8127 19
    public static function str_starts_with(string $haystack, string $needle): bool
8128
    {
8129 19
        if ($needle === '') {
8130 2
            return true;
8131
        }
8132
8133 19
        if ($haystack === '') {
8134
            return false;
8135
        }
8136
8137 19
        return \strpos($haystack, $needle) === 0;
8138
    }
8139
8140
    /**
8141
     * Returns true if the string begins with any of $substrings, false otherwise.
8142
     *
8143
     * - case-sensitive
8144
     *
8145
     * @param string $str        <p>The input string.</p>
8146
     * @param array  $substrings <p>Substrings to look for.</p>
8147
     *
8148
     * @psalm-pure
8149
     *
8150
     * @return bool whether or not $str starts with $substring
8151
     */
8152 8
    public static function str_starts_with_any(string $str, array $substrings): bool
8153
    {
8154 8
        if ($str === '') {
8155
            return false;
8156
        }
8157
8158 8
        if ($substrings === []) {
8159
            return false;
8160
        }
8161
8162 8
        foreach ($substrings as &$substring) {
8163 8
            if (self::str_starts_with($str, $substring)) {
8164 8
                return true;
8165
            }
8166
        }
8167
8168 6
        return false;
8169
    }
8170
8171
    /**
8172
     * Gets the substring after the first occurrence of a separator.
8173
     *
8174
     * @param string $str       <p>The input string.</p>
8175
     * @param string $separator <p>The string separator.</p>
8176
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
8177
     *
8178
     * @psalm-pure
8179
     *
8180
     * @return string
8181
     */
8182 1
    public static function str_substr_after_first_separator(string $str, string $separator, string $encoding = 'UTF-8'): string
8183
    {
8184 1
        if ($separator === '' || $str === '') {
8185 1
            return '';
8186
        }
8187
8188 1
        if ($encoding === 'UTF-8') {
8189 1
            $offset = \mb_strpos($str, $separator);
8190 1
            if ($offset === false) {
8191 1
                return '';
8192
            }
8193
8194 1
            return (string) \mb_substr(
8195 1
                $str,
8196 1
                $offset + (int) \mb_strlen($separator)
8197
            );
8198
        }
8199
8200
        $offset = self::strpos($str, $separator, 0, $encoding);
8201
        if ($offset === false) {
8202
            return '';
8203
        }
8204
8205
        return (string) \mb_substr(
8206
            $str,
8207
            $offset + (int) self::strlen($separator, $encoding),
8208
            null,
8209
            $encoding
8210
        );
8211
    }
8212
8213
    /**
8214
     * Gets the substring after the last occurrence of a separator.
8215
     *
8216
     * @param string $str       <p>The input string.</p>
8217
     * @param string $separator <p>The string separator.</p>
8218
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
8219
     *
8220
     * @psalm-pure
8221
     *
8222
     * @return string
8223
     */
8224 1
    public static function str_substr_after_last_separator(string $str, string $separator, string $encoding = 'UTF-8'): string
8225
    {
8226 1
        if ($separator === '' || $str === '') {
8227 1
            return '';
8228
        }
8229
8230 1
        if ($encoding === 'UTF-8') {
8231 1
            $offset = \mb_strrpos($str, $separator);
8232 1
            if ($offset === false) {
8233 1
                return '';
8234
            }
8235
8236 1
            return (string) \mb_substr(
8237 1
                $str,
8238 1
                $offset + (int) \mb_strlen($separator)
8239
            );
8240
        }
8241
8242
        $offset = self::strrpos($str, $separator, 0, $encoding);
8243
        if ($offset === false) {
8244
            return '';
8245
        }
8246
8247
        return (string) self::substr(
8248
            $str,
8249
            $offset + (int) self::strlen($separator, $encoding),
8250
            null,
8251
            $encoding
8252
        );
8253
    }
8254
8255
    /**
8256
     * Gets the substring before the first occurrence of a separator.
8257
     *
8258
     * @param string $str       <p>The input string.</p>
8259
     * @param string $separator <p>The string separator.</p>
8260
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
8261
     *
8262
     * @psalm-pure
8263
     *
8264
     * @return string
8265
     */
8266 1
    public static function str_substr_before_first_separator(
8267
        string $str,
8268
        string $separator,
8269
        string $encoding = 'UTF-8'
8270
    ): string {
8271 1
        if ($separator === '' || $str === '') {
8272 1
            return '';
8273
        }
8274
8275 1
        if ($encoding === 'UTF-8') {
8276 1
            $offset = \mb_strpos($str, $separator);
8277 1
            if ($offset === false) {
8278 1
                return '';
8279
            }
8280
8281 1
            return (string) \mb_substr(
8282 1
                $str,
8283 1
                0,
8284 1
                $offset
8285
            );
8286
        }
8287
8288
        $offset = self::strpos($str, $separator, 0, $encoding);
8289
        if ($offset === false) {
8290
            return '';
8291
        }
8292
8293
        return (string) self::substr(
8294
            $str,
8295
            0,
8296
            $offset,
8297
            $encoding
8298
        );
8299
    }
8300
8301
    /**
8302
     * Gets the substring before the last occurrence of a separator.
8303
     *
8304
     * @param string $str       <p>The input string.</p>
8305
     * @param string $separator <p>The string separator.</p>
8306
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
8307
     *
8308
     * @psalm-pure
8309
     *
8310
     * @return string
8311
     */
8312 1
    public static function str_substr_before_last_separator(string $str, string $separator, string $encoding = 'UTF-8'): string
8313
    {
8314 1
        if ($separator === '' || $str === '') {
8315 1
            return '';
8316
        }
8317
8318 1
        if ($encoding === 'UTF-8') {
8319 1
            $offset = \mb_strrpos($str, $separator);
8320 1
            if ($offset === false) {
8321 1
                return '';
8322
            }
8323
8324 1
            return (string) \mb_substr(
8325 1
                $str,
8326 1
                0,
8327 1
                $offset
8328
            );
8329
        }
8330
8331
        $offset = self::strrpos($str, $separator, 0, $encoding);
8332
        if ($offset === false) {
8333
            return '';
8334
        }
8335
8336
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
8337
8338
        return (string) self::substr(
8339
            $str,
8340
            0,
8341
            $offset,
8342
            $encoding
8343
        );
8344
    }
8345
8346
    /**
8347
     * Gets the substring after (or before via "$before_needle") the first occurrence of the "$needle".
8348
     *
8349
     * @param string $str           <p>The input string.</p>
8350
     * @param string $needle        <p>The string to look for.</p>
8351
     * @param bool   $before_needle [optional] <p>Default: false</p>
8352
     * @param string $encoding      [optional] <p>Default: 'UTF-8'</p>
8353
     *
8354
     * @psalm-pure
8355
     *
8356
     * @return string
8357
     */
8358 2
    public static function str_substr_first(
8359
        string $str,
8360
        string $needle,
8361
        bool $before_needle = false,
8362
        string $encoding = 'UTF-8'
8363
    ): string {
8364 2
        if ($str === '' || $needle === '') {
8365 2
            return '';
8366
        }
8367
8368 2
        if ($encoding === 'UTF-8') {
8369 2
            if ($before_needle) {
8370 1
                $part = \mb_strstr(
8371 1
                    $str,
8372 1
                    $needle,
8373 1
                    $before_needle
8374
                );
8375
            } else {
8376 1
                $part = \mb_strstr(
8377 1
                    $str,
8378 2
                    $needle
8379
                );
8380
            }
8381
        } else {
8382
            $part = self::strstr(
8383
                $str,
8384
                $needle,
8385
                $before_needle,
8386
                $encoding
8387
            );
8388
        }
8389
8390 2
        return $part === false ? '' : $part;
8391
    }
8392
8393
    /**
8394
     * Gets the substring after (or before via "$before_needle") the last occurrence of the "$needle".
8395
     *
8396
     * @param string $str           <p>The input string.</p>
8397
     * @param string $needle        <p>The string to look for.</p>
8398
     * @param bool   $before_needle [optional] <p>Default: false</p>
8399
     * @param string $encoding      [optional] <p>Default: 'UTF-8'</p>
8400
     *
8401
     * @psalm-pure
8402
     *
8403
     * @return string
8404
     */
8405 2
    public static function str_substr_last(
8406
        string $str,
8407
        string $needle,
8408
        bool $before_needle = false,
8409
        string $encoding = 'UTF-8'
8410
    ): string {
8411 2
        if ($str === '' || $needle === '') {
8412 2
            return '';
8413
        }
8414
8415 2
        if ($encoding === 'UTF-8') {
8416 2
            if ($before_needle) {
8417 1
                $part = \mb_strrchr(
8418 1
                    $str,
8419 1
                    $needle,
8420 1
                    $before_needle
8421
                );
8422
            } else {
8423 1
                $part = \mb_strrchr(
8424 1
                    $str,
8425 2
                    $needle
8426
                );
8427
            }
8428
        } else {
8429
            $part = self::strrchr(
8430
                $str,
8431
                $needle,
8432
                $before_needle,
8433
                $encoding
8434
            );
8435
        }
8436
8437 2
        return $part === false ? '' : $part;
8438
    }
8439
8440
    /**
8441
     * Surrounds $str with the given substring.
8442
     *
8443
     * @param string $str
8444
     * @param string $substring <p>The substring to add to both sides.</p>
8445
     *
8446
     * @psalm-pure
8447
     *
8448
     * @return string
8449
     *                <p>A string with the substring both prepended and appended.</p>
8450
     */
8451 5
    public static function str_surround(string $str, string $substring): string
8452
    {
8453 5
        return $substring . $str . $substring;
8454
    }
8455
8456
    /**
8457
     * Returns a trimmed string with the first letter of each word capitalized.
8458
     * Also accepts an array, $ignore, allowing you to list words not to be
8459
     * capitalized.
8460
     *
8461
     * @param string              $str
8462
     * @param array|string[]|null $ignore                        [optional] <p>An array of words not to capitalize or
8463
     *                                                           null. Default: null</p>
8464
     * @param string              $encoding                      [optional] <p>Default: 'UTF-8'</p>
8465
     * @param bool                $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the
8466
     *                                                           string.</p>
8467
     * @param string|null         $lang                          [optional] <p>Set the language for special cases: az,
8468
     *                                                           el, lt, tr</p>
8469
     * @param bool                $try_to_keep_the_string_length [optional] <p>true === try to keep the string length:
8470
     *                                                           e.g. ẞ -> ß</p>
8471
     * @param bool                $use_trim_first                [optional] <p>true === trim the input string,
8472
     *                                                           first</p>
8473
     * @param string|null         $word_define_chars             [optional] <p>An string of chars that will be used as
8474
     *                                                           whitespace separator === words.</p>
8475
     *
8476
     * @psalm-pure
8477
     *
8478
     * @return string
8479
     *                <p>The titleized string.</p>
8480
     *
8481
     * @noinspection PhpTooManyParametersInspection
8482
     */
8483 10
    public static function str_titleize(
8484
        string $str,
8485
        array $ignore = null,
8486
        string $encoding = 'UTF-8',
8487
        bool $clean_utf8 = false,
8488
        string $lang = null,
8489
        bool $try_to_keep_the_string_length = false,
8490
        bool $use_trim_first = true,
8491
        string $word_define_chars = null
8492
    ): string {
8493 10
        if ($str === '') {
8494
            return '';
8495
        }
8496
8497 10
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
8498 9
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
8499
        }
8500
8501 10
        if ($use_trim_first) {
8502 10
            $str = \trim($str);
8503
        }
8504
8505 10
        if ($clean_utf8) {
8506
            $str = self::clean($str);
8507
        }
8508
8509 10
        $use_mb_functions = $lang === null && !$try_to_keep_the_string_length;
8510
8511 10
        if ($word_define_chars) {
8512 4
            $word_define_chars = \preg_quote($word_define_chars, '/');
8513
        } else {
8514 6
            $word_define_chars = '';
8515
        }
8516
8517 10
        $str = (string) \preg_replace_callback(
8518 10
            '/([^\\s' . $word_define_chars . ']+)/u',
8519
            static function (array $match) use ($try_to_keep_the_string_length, $lang, $ignore, $use_mb_functions, $encoding): string {
8520 10
                if ($ignore !== null && \in_array($match[0], $ignore, true)) {
8521 4
                    return $match[0];
8522
                }
8523
8524 10
                if ($use_mb_functions) {
8525 10
                    if ($encoding === 'UTF-8') {
8526 10
                        return \mb_strtoupper(\mb_substr($match[0], 0, 1))
8527 10
                               . \mb_strtolower(\mb_substr($match[0], 1));
8528
                    }
8529
8530
                    return \mb_strtoupper(\mb_substr($match[0], 0, 1, $encoding), $encoding)
8531
                           . \mb_strtolower(\mb_substr($match[0], 1, null, $encoding), $encoding);
8532
                }
8533
8534
                return self::ucfirst(
8535
                    self::strtolower(
8536
                        $match[0],
8537
                        $encoding,
8538
                        false,
8539
                        $lang,
8540
                        $try_to_keep_the_string_length
8541
                    ),
8542
                    $encoding,
8543
                    false,
8544
                    $lang,
8545
                    $try_to_keep_the_string_length
8546
                );
8547 10
            },
8548 10
            $str
8549
        );
8550
8551 10
        return $str;
8552
    }
8553
8554
    /**
8555
     * Returns a trimmed string in proper title case.
8556
     *
8557
     * Also accepts an array, $ignore, allowing you to list words not to be
8558
     * capitalized.
8559
     *
8560
     * Adapted from John Gruber's script.
8561
     *
8562
     * @see https://gist.github.com/gruber/9f9e8650d68b13ce4d78
8563
     *
8564
     * @param string $str
8565
     * @param array  $ignore   <p>An array of words not to capitalize.</p>
8566
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
8567
     *
8568
     * @psalm-pure
8569
     *
8570
     * @return string
8571
     *                <p>The titleized string.</p>
8572
     */
8573 35
    public static function str_titleize_for_humans(
8574
        string $str,
8575
        array $ignore = [],
8576
        string $encoding = 'UTF-8'
8577
    ): string {
8578 35
        if ($str === '') {
8579
            return '';
8580
        }
8581
8582
        $small_words = [
8583 35
            '(?<!q&)a',
8584
            'an',
8585
            'and',
8586
            'as',
8587
            'at(?!&t)',
8588
            'but',
8589
            'by',
8590
            'en',
8591
            'for',
8592
            'if',
8593
            'in',
8594
            'of',
8595
            'on',
8596
            'or',
8597
            'the',
8598
            'to',
8599
            'v[.]?',
8600
            'via',
8601
            'vs[.]?',
8602
        ];
8603
8604 35
        if ($ignore !== []) {
8605 1
            $small_words = \array_merge($small_words, $ignore);
8606
        }
8607
8608 35
        $small_words_rx = \implode('|', $small_words);
8609 35
        $apostrophe_rx = '(?x: [\'’] [[:lower:]]* )?';
8610
8611 35
        $str = \trim($str);
8612
8613 35
        if (!self::has_lowercase($str)) {
8614 2
            $str = self::strtolower($str, $encoding);
8615
        }
8616
8617
        // the main substitutions
8618
        /** @noinspection RegExpDuplicateAlternationBranch - false-positive - https://youtrack.jetbrains.com/issue/WI-51002 */
8619 35
        $str = (string) \preg_replace_callback(
8620
            '~\\b (_*) (?:                                                           # 1. Leading underscore and
8621
                        ( (?<=[ ][/\\\\]) [[:alpha:]]+ [-_[:alpha:]/\\\\]+ |                # 2. file path or 
8622 35
                          [-_[:alpha:]]+ [@.:] [-_[:alpha:]@.:/]+ ' . $apostrophe_rx . ' )  #    URL, domain, or email
8623
                        |
8624 35
                        ( (?i: ' . $small_words_rx . ' ) ' . $apostrophe_rx . ' )           # 3. or small word (case-insensitive)
8625
                        |
8626 35
                        ( [[:alpha:]] [[:lower:]\'’()\[\]{}]* ' . $apostrophe_rx . ' )     # 4. or word w/o internal caps
8627
                        |
8628 35
                        ( [[:alpha:]] [[:alpha:]\'’()\[\]{}]* ' . $apostrophe_rx . ' )     # 5. or some other word
8629
                      ) (_*) \\b                                                          # 6. With trailing underscore
8630
                    ~ux',
8631
            /**
8632
             * @param string[] $matches
8633
             *
8634
             * @psalm-pure
8635
             *
8636
             * @return string
8637
             */
8638
            static function (array $matches) use ($encoding): string {
8639
                // preserve leading underscore
8640 35
                $str = $matches[1];
8641 35
                if ($matches[2]) {
8642
                    // preserve URLs, domains, emails and file paths
8643 5
                    $str .= $matches[2];
8644 35
                } elseif ($matches[3]) {
8645
                    // lower-case small words
8646 25
                    $str .= self::strtolower($matches[3], $encoding);
8647 35
                } elseif ($matches[4]) {
8648
                    // capitalize word w/o internal caps
8649 34
                    $str .= static::ucfirst($matches[4], $encoding);
8650
                } else {
8651
                    // preserve other kinds of word (iPhone)
8652 7
                    $str .= $matches[5];
8653
                }
8654
                // preserve trailing underscore
8655 35
                $str .= $matches[6];
8656
8657 35
                return $str;
8658 35
            },
8659 35
            $str
8660
        );
8661
8662
        // Exceptions for small words: capitalize at start of title...
8663 35
        $str = (string) \preg_replace_callback(
8664
            '~(  \\A [[:punct:]]*            # start of title...
8665
                      |  [:.;?!][ ]+                # or of subsentence...
8666
                      |  [ ][\'"“‘(\[][ ]* )        # or of inserted subphrase...
8667 35
                      ( ' . $small_words_rx . ' ) \\b # ...followed by small word
8668
                     ~uxi',
8669
            /**
8670
             * @param string[] $matches
8671
             *
8672
             * @psalm-pure
8673
             *
8674
             * @return string
8675
             */
8676
            static function (array $matches) use ($encoding): string {
8677 11
                return $matches[1] . static::ucfirst($matches[2], $encoding);
8678 35
            },
8679 35
            $str
8680
        );
8681
8682
        // ...and end of title
8683 35
        $str = (string) \preg_replace_callback(
8684 35
            '~\\b ( ' . $small_words_rx . ' ) # small word...
8685
                      (?= [[:punct:]]* \Z          # ...at the end of the title...
8686
                      |   [\'"’”)\]] [ ] )         # ...or of an inserted subphrase?
8687
                     ~uxi',
8688
            /**
8689
             * @param string[] $matches
8690
             *
8691
             * @psalm-pure
8692
             *
8693
             * @return string
8694
             */
8695
            static function (array $matches) use ($encoding): string {
8696 3
                return static::ucfirst($matches[1], $encoding);
8697 35
            },
8698 35
            $str
8699
        );
8700
8701
        // Exceptions for small words in hyphenated compound words.
8702
        // e.g. "in-flight" -> In-Flight
8703 35
        $str = (string) \preg_replace_callback(
8704
            '~\\b
8705
                        (?<! -)                   # Negative lookbehind for a hyphen; we do not want to match man-in-the-middle but do want (in-flight)
8706 35
                        ( ' . $small_words_rx . ' )
8707
                        (?= -[[:alpha:]]+)        # lookahead for "-someword"
8708
                       ~uxi',
8709
            /**
8710
             * @param string[] $matches
8711
             *
8712
             * @psalm-pure
8713
             *
8714
             * @return string
8715
             */
8716
            static function (array $matches) use ($encoding): string {
8717
                return static::ucfirst($matches[1], $encoding);
8718 35
            },
8719 35
            $str
8720
        );
8721
8722
        // e.g. "Stand-in" -> "Stand-In" (Stand is already capped at this point)
8723 35
        $str = (string) \preg_replace_callback(
8724
            '~\\b
8725
                      (?<!…)                    # Negative lookbehind for a hyphen; we do not want to match man-in-the-middle but do want (stand-in)
8726
                      ( [[:alpha:]]+- )         # $1 = first word and hyphen, should already be properly capped
8727 35
                      ( ' . $small_words_rx . ' ) # ...followed by small word
8728
                      (?!	- )                 # Negative lookahead for another -
8729
                     ~uxi',
8730
            /**
8731
             * @param string[] $matches
8732
             *
8733
             * @psalm-pure
8734
             *
8735
             * @return string
8736
             */
8737
            static function (array $matches) use ($encoding): string {
8738
                return $matches[1] . static::ucfirst($matches[2], $encoding);
8739 35
            },
8740 35
            $str
8741
        );
8742
8743 35
        return $str;
8744
    }
8745
8746
    /**
8747
     * Get a binary representation of a specific string.
8748
     *
8749
     * @param string $str <p>The input string.</p>
8750
     *
8751
     * @psalm-pure
8752
     *
8753
     * @return false|string
8754
     *                      <p>false on error</p>
8755
     */
8756 2
    public static function str_to_binary(string $str)
8757
    {
8758
        /** @var array|false $value - needed for PhpStan (stubs error) */
8759 2
        $value = \unpack('H*', $str);
8760 2
        if ($value === false) {
8761
            return false;
8762
        }
8763
8764
        /** @noinspection OffsetOperationsInspection */
8765 2
        return \base_convert($value[1], 16, 2);
8766
    }
8767
8768
    /**
8769
     * @param string   $str
8770
     * @param bool     $remove_empty_values <p>Remove empty values.</p>
8771
     * @param int|null $remove_short_values <p>The min. string length or null to disable</p>
8772
     *
8773
     * @psalm-pure
8774
     *
8775
     * @return string[]
8776
     */
8777 17
    public static function str_to_lines(string $str, bool $remove_empty_values = false, int $remove_short_values = null): array
8778
    {
8779 17
        if ($str === '') {
8780 1
            return $remove_empty_values ? [] : [''];
8781
        }
8782
8783 16
        if (self::$SUPPORT['mbstring'] === true) {
8784
            /** @noinspection PhpComposerExtensionStubsInspection */
8785 16
            $return = \mb_split("[\r\n]{1,2}", $str);
8786
        } else {
8787
            $return = \preg_split("/[\r\n]{1,2}/u", $str);
8788
        }
8789
8790 16
        if ($return === false) {
8791
            return $remove_empty_values ? [] : [''];
8792
        }
8793
8794
        if (
8795 16
            $remove_short_values === null
8796
            &&
8797 16
            !$remove_empty_values
8798
        ) {
8799 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...
8800
        }
8801
8802
        return self::reduce_string_array(
8803
            $return,
8804
            $remove_empty_values,
8805
            $remove_short_values
8806
        );
8807
    }
8808
8809
    /**
8810
     * Convert a string into an array of words.
8811
     *
8812
     * @param string   $str
8813
     * @param string   $char_list           <p>Additional chars for the definition of "words".</p>
8814
     * @param bool     $remove_empty_values <p>Remove empty values.</p>
8815
     * @param int|null $remove_short_values <p>The min. string length or null to disable</p>
8816
     *
8817
     * @psalm-pure
8818
     *
8819
     * @return string[]
8820
     */
8821 13
    public static function str_to_words(
8822
        string $str,
8823
        string $char_list = '',
8824
        bool $remove_empty_values = false,
8825
        int $remove_short_values = null
8826
    ): array {
8827 13
        if ($str === '') {
8828 4
            return $remove_empty_values ? [] : [''];
8829
        }
8830
8831 13
        $char_list = self::rxClass($char_list, '\pL');
8832
8833 13
        $return = \preg_split("/({$char_list}+(?:[\p{Pd}’']{$char_list}+)*)/u", $str, -1, \PREG_SPLIT_DELIM_CAPTURE);
8834 13
        if ($return === false) {
8835
            return $remove_empty_values ? [] : [''];
8836
        }
8837
8838
        if (
8839 13
            $remove_short_values === null
8840
            &&
8841 13
            !$remove_empty_values
8842
        ) {
8843 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...
8844
        }
8845
8846 2
        $tmp_return = self::reduce_string_array(
8847 2
            $return,
8848 2
            $remove_empty_values,
8849 2
            $remove_short_values
8850
        );
8851
8852 2
        foreach ($tmp_return as &$item) {
8853 2
            $item = (string) $item;
8854
        }
8855
8856 2
        return $tmp_return;
8857
    }
8858
8859
    /**
8860
     * alias for "UTF8::to_ascii()"
8861
     *
8862
     * @param string $str
8863
     * @param string $unknown
8864
     * @param bool   $strict
8865
     *
8866
     * @psalm-pure
8867
     *
8868
     * @return string
8869
     *
8870
     * @see        UTF8::to_ascii()
8871
     * @deprecated <p>please use "UTF8::to_ascii()"</p>
8872
     */
8873 7
    public static function str_transliterate(
8874
        string $str,
8875
        string $unknown = '?',
8876
        bool $strict = false
8877
    ): string {
8878 7
        return self::to_ascii($str, $unknown, $strict);
8879
    }
8880
8881
    /**
8882
     * Truncates the string to a given length. If $substring is provided, and
8883
     * truncating occurs, the string is further truncated so that the substring
8884
     * may be appended without exceeding the desired length.
8885
     *
8886
     * @param string $str
8887
     * @param int    $length    <p>Desired length of the truncated string.</p>
8888
     * @param string $substring [optional] <p>The substring to append if it can fit. Default: ''</p>
8889
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
8890
     *
8891
     * @psalm-pure
8892
     *
8893
     * @return string
8894
     *                <p>A string after truncating.</p>
8895
     */
8896 22
    public static function str_truncate(
8897
        string $str,
8898
        int $length,
8899
        string $substring = '',
8900
        string $encoding = 'UTF-8'
8901
    ): string {
8902 22
        if ($str === '') {
8903
            return '';
8904
        }
8905
8906 22
        if ($encoding === 'UTF-8') {
8907 10
            if ($length >= (int) \mb_strlen($str)) {
8908 2
                return $str;
8909
            }
8910
8911 8
            if ($substring !== '') {
8912 4
                $length -= (int) \mb_strlen($substring);
8913
8914
                /** @noinspection UnnecessaryCastingInspection */
8915 4
                return (string) \mb_substr($str, 0, $length) . $substring;
8916
            }
8917
8918
            /** @noinspection UnnecessaryCastingInspection */
8919 4
            return (string) \mb_substr($str, 0, $length);
8920
        }
8921
8922 12
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
8923
8924 12
        if ($length >= (int) self::strlen($str, $encoding)) {
8925 2
            return $str;
8926
        }
8927
8928 10
        if ($substring !== '') {
8929 6
            $length -= (int) self::strlen($substring, $encoding);
8930
        }
8931
8932
        return (
8933 10
               (string) self::substr(
8934 10
                   $str,
8935 10
                   0,
8936 10
                   $length,
8937 10
                   $encoding
8938
               )
8939 10
               ) . $substring;
8940
    }
8941
8942
    /**
8943
     * Truncates the string to a given length, while ensuring that it does not
8944
     * split words. If $substring is provided, and truncating occurs, the
8945
     * string is further truncated so that the substring may be appended without
8946
     * exceeding the desired length.
8947
     *
8948
     * @param string $str
8949
     * @param int    $length                                 <p>Desired length of the truncated string.</p>
8950
     * @param string $substring                              [optional] <p>The substring to append if it can fit.
8951
     *                                                       Default:
8952
     *                                                       ''</p>
8953
     * @param string $encoding                               [optional] <p>Default: 'UTF-8'</p>
8954
     * @param bool   $ignore_do_not_split_words_for_one_word [optional] <p>Default: false</p>
8955
     *
8956
     * @psalm-pure
8957
     *
8958
     * @return string
8959
     *                <p>A string after truncating.</p>
8960
     */
8961 47
    public static function str_truncate_safe(
8962
        string $str,
8963
        int $length,
8964
        string $substring = '',
8965
        string $encoding = 'UTF-8',
8966
        bool $ignore_do_not_split_words_for_one_word = false
8967
    ): string {
8968 47
        if ($str === '' || $length <= 0) {
8969 1
            return $substring;
8970
        }
8971
8972 47
        if ($encoding === 'UTF-8') {
8973 21
            if ($length >= (int) \mb_strlen($str)) {
8974 5
                return $str;
8975
            }
8976
8977
            // need to further trim the string so we can append the substring
8978 17
            $length -= (int) \mb_strlen($substring);
8979 17
            if ($length <= 0) {
8980 1
                return $substring;
8981
            }
8982
8983
            /** @var false|string $truncated - needed for PhpStan (stubs error) */
8984 17
            $truncated = \mb_substr($str, 0, $length);
8985 17
            if ($truncated === false) {
8986
                return '';
8987
            }
8988
8989
            // if the last word was truncated
8990 17
            $space_position = \mb_strpos($str, ' ', $length - 1);
8991 17
            if ($space_position !== $length) {
8992
                // find pos of the last occurrence of a space, get up to that
8993 13
                $last_position = \mb_strrpos($truncated, ' ', 0);
8994
8995
                if (
8996 13
                    $last_position !== false
8997
                    ||
8998
                    (
8999 3
                        $space_position !== false
9000
                        &&
9001 13
                         !$ignore_do_not_split_words_for_one_word
9002
                    )
9003
                ) {
9004 17
                    $truncated = (string) \mb_substr($truncated, 0, (int) $last_position);
9005
                }
9006
            }
9007
        } else {
9008 26
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9009
9010 26
            if ($length >= (int) self::strlen($str, $encoding)) {
9011 4
                return $str;
9012
            }
9013
9014
            // need to further trim the string so we can append the substring
9015 22
            $length -= (int) self::strlen($substring, $encoding);
9016 22
            if ($length <= 0) {
9017
                return $substring;
9018
            }
9019
9020 22
            $truncated = self::substr($str, 0, $length, $encoding);
9021
9022 22
            if ($truncated === false) {
9023
                return '';
9024
            }
9025
9026
            // if the last word was truncated
9027 22
            $space_position = self::strpos($str, ' ', $length - 1, $encoding);
9028 22
            if ($space_position !== $length) {
9029
                // find pos of the last occurrence of a space, get up to that
9030 12
                $last_position = self::strrpos($truncated, ' ', 0, $encoding);
9031
9032
                if (
9033 12
                    $last_position !== false
9034
                    ||
9035
                    (
9036 4
                        $space_position !== false
9037
                        &&
9038 12
                        !$ignore_do_not_split_words_for_one_word
9039
                    )
9040
                ) {
9041 9
                    $truncated = (string) self::substr($truncated, 0, (int) $last_position, $encoding);
9042
                }
9043
            }
9044
        }
9045
9046 39
        return $truncated . $substring;
9047
    }
9048
9049
    /**
9050
     * Returns a lowercase and trimmed string separated by underscores.
9051
     * Underscores are inserted before uppercase characters (with the exception
9052
     * of the first character of the string), and in place of spaces as well as
9053
     * dashes.
9054
     *
9055
     * @param string $str
9056
     *
9057
     * @psalm-pure
9058
     *
9059
     * @return string
9060
     *                <p>The underscored string.</p>
9061
     */
9062 16
    public static function str_underscored(string $str): string
9063
    {
9064 16
        return self::str_delimit($str, '_');
9065
    }
9066
9067
    /**
9068
     * Returns an UpperCamelCase version of the supplied string. It trims
9069
     * surrounding spaces, capitalizes letters following digits, spaces, dashes
9070
     * and underscores, and removes spaces, dashes, underscores.
9071
     *
9072
     * @param string      $str                           <p>The input string.</p>
9073
     * @param string      $encoding                      [optional] <p>Default: 'UTF-8'</p>
9074
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
9075
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt,
9076
     *                                                   tr</p>
9077
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ
9078
     *                                                   -> ß</p>
9079
     *
9080
     * @psalm-pure
9081
     *
9082
     * @return string
9083
     *                <p>A string in UpperCamelCase.</p>
9084
     */
9085 13
    public static function str_upper_camelize(
9086
        string $str,
9087
        string $encoding = 'UTF-8',
9088
        bool $clean_utf8 = false,
9089
        string $lang = null,
9090
        bool $try_to_keep_the_string_length = false
9091
    ): string {
9092 13
        return self::ucfirst(self::str_camelize($str, $encoding), $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
9093
    }
9094
9095
    /**
9096
     * alias for "UTF8::ucfirst()"
9097
     *
9098
     * @param string      $str
9099
     * @param string      $encoding
9100
     * @param bool        $clean_utf8
9101
     * @param string|null $lang
9102
     * @param bool        $try_to_keep_the_string_length
9103
     *
9104
     * @psalm-pure
9105
     *
9106
     * @return string
9107
     *
9108
     * @see        UTF8::ucfirst()
9109
     * @deprecated <p>please use "UTF8::ucfirst()"</p>
9110
     */
9111 5
    public static function str_upper_first(
9112
        string $str,
9113
        string $encoding = 'UTF-8',
9114
        bool $clean_utf8 = false,
9115
        string $lang = null,
9116
        bool $try_to_keep_the_string_length = false
9117
    ): string {
9118 5
        return self::ucfirst(
9119 5
            $str,
9120 5
            $encoding,
9121 5
            $clean_utf8,
9122 5
            $lang,
9123 5
            $try_to_keep_the_string_length
9124
        );
9125
    }
9126
9127
    /**
9128
     * Get the number of words in a specific string.
9129
     *
9130
     * @param string $str       <p>The input string.</p>
9131
     * @param int    $format    [optional] <p>
9132
     *                          <strong>0</strong> => return a number of words (default)<br>
9133
     *                          <strong>1</strong> => return an array of words<br>
9134
     *                          <strong>2</strong> => return an array of words with word-offset as key
9135
     *                          </p>
9136
     * @param string $char_list [optional] <p>Additional chars that contains to words and do not start a new word.</p>
9137
     *
9138
     * @psalm-pure
9139
     *
9140
     * @return int|string[]
9141
     *                      <p>The number of words in the string.</p>
9142
     */
9143 2
    public static function str_word_count(string $str, int $format = 0, string $char_list = '')
9144
    {
9145 2
        $str_parts = self::str_to_words($str, $char_list);
9146
9147 2
        $len = \count($str_parts);
9148
9149 2
        if ($format === 1) {
9150 2
            $number_of_words = [];
9151 2
            for ($i = 1; $i < $len; $i += 2) {
9152 2
                $number_of_words[] = $str_parts[$i];
9153
            }
9154 2
        } elseif ($format === 2) {
9155 2
            $number_of_words = [];
9156 2
            $offset = (int) self::strlen($str_parts[0]);
9157 2
            for ($i = 1; $i < $len; $i += 2) {
9158 2
                $number_of_words[$offset] = $str_parts[$i];
9159 2
                $offset += (int) self::strlen($str_parts[$i]) + (int) self::strlen($str_parts[$i + 1]);
9160
            }
9161
        } else {
9162 2
            $number_of_words = (int) (($len - 1) / 2);
9163
        }
9164
9165 2
        return $number_of_words;
9166
    }
9167
9168
    /**
9169
     * Case-insensitive string comparison.
9170
     *
9171
     * INFO: Case-insensitive version of UTF8::strcmp()
9172
     *
9173
     * @param string $str1     <p>The first string.</p>
9174
     * @param string $str2     <p>The second string.</p>
9175
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
9176
     *
9177
     * @psalm-pure
9178
     *
9179
     * @return int
9180
     *             <strong>&lt; 0</strong> if str1 is less than str2;<br>
9181
     *             <strong>&gt; 0</strong> if str1 is greater than str2,<br>
9182
     *             <strong>0</strong> if they are equal
9183
     */
9184 23
    public static function strcasecmp(
9185
        string $str1,
9186
        string $str2,
9187
        string $encoding = 'UTF-8'
9188
    ): int {
9189 23
        return self::strcmp(
9190 23
            self::strtocasefold(
9191 23
                $str1,
9192 23
                true,
9193 23
                false,
9194 23
                $encoding,
9195 23
                null,
9196 23
                false
9197
            ),
9198 23
            self::strtocasefold(
9199 23
                $str2,
9200 23
                true,
9201 23
                false,
9202 23
                $encoding,
9203 23
                null,
9204 23
                false
9205
            )
9206
        );
9207
    }
9208
9209
    /**
9210
     * alias for "UTF8::strstr()"
9211
     *
9212
     * @param string $haystack
9213
     * @param string $needle
9214
     * @param bool   $before_needle
9215
     * @param string $encoding
9216
     * @param bool   $clean_utf8
9217
     *
9218
     * @psalm-pure
9219
     *
9220
     * @return false|string
9221
     *
9222
     * @see        UTF8::strstr()
9223
     * @deprecated <p>please use "UTF8::strstr()"</p>
9224
     */
9225 2
    public static function strchr(
9226
        string $haystack,
9227
        string $needle,
9228
        bool $before_needle = false,
9229
        string $encoding = 'UTF-8',
9230
        bool $clean_utf8 = false
9231
    ) {
9232 2
        return self::strstr(
9233 2
            $haystack,
9234 2
            $needle,
9235 2
            $before_needle,
9236 2
            $encoding,
9237 2
            $clean_utf8
9238
        );
9239
    }
9240
9241
    /**
9242
     * Case-sensitive string comparison.
9243
     *
9244
     * @param string $str1 <p>The first string.</p>
9245
     * @param string $str2 <p>The second string.</p>
9246
     *
9247
     * @psalm-pure
9248
     *
9249
     * @return int
9250
     *             <strong>&lt; 0</strong> if str1 is less than str2<br>
9251
     *             <strong>&gt; 0</strong> if str1 is greater than str2<br>
9252
     *             <strong>0</strong> if they are equal
9253
     */
9254 29
    public static function strcmp(string $str1, string $str2): int
9255
    {
9256 29
        if ($str1 === $str2) {
9257 21
            return 0;
9258
        }
9259
9260 24
        return \strcmp(
9261 24
            \Normalizer::normalize($str1, \Normalizer::NFD),
9262 24
            \Normalizer::normalize($str2, \Normalizer::NFD)
9263
        );
9264
    }
9265
9266
    /**
9267
     * Find length of initial segment not matching mask.
9268
     *
9269
     * @param string $str
9270
     * @param string $char_list
9271
     * @param int    $offset
9272
     * @param int    $length
9273
     * @param string $encoding  [optional] <p>Set the charset for e.g. "mb_" function</p>
9274
     *
9275
     * @psalm-pure
9276
     *
9277
     * @return int
9278
     */
9279 12
    public static function strcspn(
9280
        string $str,
9281
        string $char_list,
9282
        int $offset = null,
9283
        int $length = null,
9284
        string $encoding = 'UTF-8'
9285
    ): int {
9286 12
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9287
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9288
        }
9289
9290 12
        if ($char_list === '') {
9291 2
            return (int) self::strlen($str, $encoding);
9292
        }
9293
9294 11
        if ($offset !== null || $length !== null) {
9295 3
            if ($encoding === 'UTF-8') {
9296 3
                if ($length === null) {
9297
                    /** @noinspection UnnecessaryCastingInspection */
9298 2
                    $str_tmp = \mb_substr($str, (int) $offset);
9299
                } else {
9300
                    /** @noinspection UnnecessaryCastingInspection */
9301 3
                    $str_tmp = \mb_substr($str, (int) $offset, $length);
9302
                }
9303
            } else {
9304
                /** @noinspection UnnecessaryCastingInspection */
9305
                $str_tmp = self::substr($str, (int) $offset, $length, $encoding);
9306
            }
9307
9308 3
            if ($str_tmp === false) {
9309
                return 0;
9310
            }
9311
9312
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
9313 3
            $str = $str_tmp;
9314
        }
9315
9316 11
        if ($str === '') {
9317 2
            return 0;
9318
        }
9319
9320 10
        $matches = [];
9321 10
        if (\preg_match('/^(.*?)' . self::rxClass($char_list) . '/us', $str, $matches)) {
9322 9
            $return = self::strlen($matches[1], $encoding);
9323 9
            if ($return === false) {
9324
                return 0;
9325
            }
9326
9327 9
            return $return;
9328
        }
9329
9330 2
        return (int) self::strlen($str, $encoding);
9331
    }
9332
9333
    /**
9334
     * alias for "UTF8::stristr()"
9335
     *
9336
     * @param string $haystack
9337
     * @param string $needle
9338
     * @param bool   $before_needle
9339
     * @param string $encoding
9340
     * @param bool   $clean_utf8
9341
     *
9342
     * @psalm-pure
9343
     *
9344
     * @return false|string
9345
     *
9346
     * @see        UTF8::stristr()
9347
     * @deprecated <p>please use "UTF8::stristr()"</p>
9348
     */
9349 1
    public static function strichr(
9350
        string $haystack,
9351
        string $needle,
9352
        bool $before_needle = false,
9353
        string $encoding = 'UTF-8',
9354
        bool $clean_utf8 = false
9355
    ) {
9356 1
        return self::stristr(
9357 1
            $haystack,
9358 1
            $needle,
9359 1
            $before_needle,
9360 1
            $encoding,
9361 1
            $clean_utf8
9362
        );
9363
    }
9364
9365
    /**
9366
     * Create a UTF-8 string from code points.
9367
     *
9368
     * INFO: opposite to UTF8::codepoints()
9369
     *
9370
     * @param array $array <p>Integer or Hexadecimal codepoints.</p>
9371
     *
9372
     * @psalm-pure
9373
     *
9374
     * @return string
9375
     *                <p>A UTF-8 encoded string.</p>
9376
     */
9377 4
    public static function string(array $array): string
9378
    {
9379 4
        if ($array === []) {
9380 4
            return '';
9381
        }
9382
9383 4
        $str = '';
9384 4
        foreach ($array as $strPart) {
9385 4
            $str .= '&#' . (int) $strPart . ';';
9386
        }
9387
9388 4
        return self::html_entity_decode($str, \ENT_QUOTES | \ENT_HTML5);
9389
    }
9390
9391
    /**
9392
     * Checks if string starts with "BOM" (Byte Order Mark Character) character.
9393
     *
9394
     * @param string $str <p>The input string.</p>
9395
     *
9396
     * @psalm-pure
9397
     *
9398
     * @return bool
9399
     *              <strong>true</strong> if the string has BOM at the start,<br>
9400
     *              <strong>false</strong> otherwise
9401
     */
9402 6
    public static function string_has_bom(string $str): bool
9403
    {
9404
        /** @noinspection PhpUnusedLocalVariableInspection */
9405 6
        foreach (self::$BOM as $bom_string => &$bom_byte_length) {
9406 6
            if (\strpos($str, $bom_string) === 0) {
9407 6
                return true;
9408
            }
9409
        }
9410
9411 6
        return false;
9412
    }
9413
9414
    /**
9415
     * Strip HTML and PHP tags from a string + clean invalid UTF-8.
9416
     *
9417
     * @see http://php.net/manual/en/function.strip-tags.php
9418
     *
9419
     * @param string $str            <p>
9420
     *                               The input string.
9421
     *                               </p>
9422
     * @param string $allowable_tags [optional] <p>
9423
     *                               You can use the optional second parameter to specify tags which should
9424
     *                               not be stripped.
9425
     *                               </p>
9426
     *                               <p>
9427
     *                               HTML comments and PHP tags are also stripped. This is hardcoded and
9428
     *                               can not be changed with allowable_tags.
9429
     *                               </p>
9430
     * @param bool   $clean_utf8     [optional] <p>Remove non UTF-8 chars from the string.</p>
9431
     *
9432
     * @psalm-pure
9433
     *
9434
     * @return string
9435
     *                <p>The stripped string.</p>
9436
     */
9437 4
    public static function strip_tags(
9438
        string $str,
9439
        string $allowable_tags = null,
9440
        bool $clean_utf8 = false
9441
    ): string {
9442 4
        if ($str === '') {
9443 1
            return '';
9444
        }
9445
9446 4
        if ($clean_utf8) {
9447 2
            $str = self::clean($str);
9448
        }
9449
9450 4
        if ($allowable_tags === null) {
9451 4
            return \strip_tags($str);
9452
        }
9453
9454 2
        return \strip_tags($str, $allowable_tags);
9455
    }
9456
9457
    /**
9458
     * Strip all whitespace characters. This includes tabs and newline
9459
     * characters, as well as multibyte whitespace such as the thin space
9460
     * and ideographic space.
9461
     *
9462
     * @param string $str
9463
     *
9464
     * @psalm-pure
9465
     *
9466
     * @return string
9467
     */
9468 36
    public static function strip_whitespace(string $str): string
9469
    {
9470 36
        if ($str === '') {
9471 3
            return '';
9472
        }
9473
9474 33
        return (string) \preg_replace('/[[:space:]]+/u', '', $str);
9475
    }
9476
9477
    /**
9478
     * Find the position of the first occurrence of a substring in a string, case-insensitive.
9479
     *
9480
     * @see http://php.net/manual/en/function.mb-stripos.php
9481
     *
9482
     * @param string $haystack   <p>The string from which to get the position of the first occurrence of needle.</p>
9483
     * @param string $needle     <p>The string to find in haystack.</p>
9484
     * @param int    $offset     [optional] <p>The position in haystack to start searching.</p>
9485
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
9486
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
9487
     *
9488
     * @psalm-pure
9489
     *
9490
     * @return false|int
9491
     *                   Return the <strong>(int)</strong> numeric position of the first occurrence of needle in the
9492
     *                   haystack string,<br> or <strong>false</strong> if needle is not found
9493
     */
9494 25
    public static function stripos(
9495
        string $haystack,
9496
        string $needle,
9497
        int $offset = 0,
9498
        string $encoding = 'UTF-8',
9499
        bool $clean_utf8 = false
9500
    ) {
9501 25
        if ($haystack === '' || $needle === '') {
9502 5
            return false;
9503
        }
9504
9505 24
        if ($clean_utf8) {
9506
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
9507
            // if invalid characters are found in $haystack before $needle
9508 1
            $haystack = self::clean($haystack);
9509 1
            $needle = self::clean($needle);
9510
        }
9511
9512 24
        if (self::$SUPPORT['mbstring'] === true) {
9513 24
            if ($encoding === 'UTF-8') {
9514 24
                return \mb_stripos($haystack, $needle, $offset);
9515
            }
9516
9517 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9518
9519 2
            return \mb_stripos($haystack, $needle, $offset, $encoding);
9520
        }
9521
9522 2
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
9523
9524
        if (
9525 2
            $encoding === 'UTF-8' // INFO: "grapheme_stripos()" can't handle other encodings
9526
            &&
9527 2
            $offset >= 0 // grapheme_stripos() can't handle negative offset
9528
            &&
9529 2
            self::$SUPPORT['intl'] === true
9530
        ) {
9531
            $return_tmp = \grapheme_stripos($haystack, $needle, $offset);
9532
            if ($return_tmp !== false) {
9533
                return $return_tmp;
9534
            }
9535
        }
9536
9537
        //
9538
        // fallback for ascii only
9539
        //
9540
9541 2
        if (ASCII::is_ascii($haystack . $needle)) {
9542
            return \stripos($haystack, $needle, $offset);
9543
        }
9544
9545
        //
9546
        // fallback via vanilla php
9547
        //
9548
9549 2
        $haystack = self::strtocasefold($haystack, true, false, $encoding, null, false);
9550 2
        $needle = self::strtocasefold($needle, true, false, $encoding, null, false);
9551
9552 2
        return self::strpos($haystack, $needle, $offset, $encoding);
9553
    }
9554
9555
    /**
9556
     * Returns all of haystack starting from and including the first occurrence of needle to the end.
9557
     *
9558
     * @param string $haystack      <p>The input string. Must be valid UTF-8.</p>
9559
     * @param string $needle        <p>The string to look for. Must be valid UTF-8.</p>
9560
     * @param bool   $before_needle [optional] <p>
9561
     *                              If <b>TRUE</b>, it returns the part of the
9562
     *                              haystack before the first occurrence of the needle (excluding the needle).
9563
     *                              </p>
9564
     * @param string $encoding      [optional] <p>Set the charset for e.g. "mb_" function</p>
9565
     * @param bool   $clean_utf8    [optional] <p>Remove non UTF-8 chars from the string.</p>
9566
     *
9567
     * @psalm-pure
9568
     *
9569
     * @return false|string
9570
     *                      <p>A sub-string,<br>or <strong>false</strong> if needle is not found.</p>
9571
     */
9572 12
    public static function stristr(
9573
        string $haystack,
9574
        string $needle,
9575
        bool $before_needle = false,
9576
        string $encoding = 'UTF-8',
9577
        bool $clean_utf8 = false
9578
    ) {
9579 12
        if ($haystack === '' || $needle === '') {
9580 3
            return false;
9581
        }
9582
9583 9
        if ($clean_utf8) {
9584
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
9585
            // if invalid characters are found in $haystack before $needle
9586 1
            $needle = self::clean($needle);
9587 1
            $haystack = self::clean($haystack);
9588
        }
9589
9590 9
        if (!$needle) {
9591
            return $haystack;
9592
        }
9593
9594 9
        if (self::$SUPPORT['mbstring'] === true) {
9595 9
            if ($encoding === 'UTF-8') {
9596 9
                return \mb_stristr($haystack, $needle, $before_needle);
9597
            }
9598
9599 1
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9600
9601 1
            return \mb_stristr($haystack, $needle, $before_needle, $encoding);
9602
        }
9603
9604
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
9605
9606
        if (
9607
            $encoding !== 'UTF-8'
9608
            &&
9609
            self::$SUPPORT['mbstring'] === false
9610
        ) {
9611
            /**
9612
             * @psalm-suppress ImpureFunctionCall - is is only a warning
9613
             */
9614
            \trigger_error('UTF8::stristr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
9615
        }
9616
9617
        if (
9618
            $encoding === 'UTF-8' // INFO: "grapheme_stristr()" can't handle other encodings
9619
            &&
9620
            self::$SUPPORT['intl'] === true
9621
        ) {
9622
            $return_tmp = \grapheme_stristr($haystack, $needle, $before_needle);
9623
            if ($return_tmp !== false) {
9624
                return $return_tmp;
9625
            }
9626
        }
9627
9628
        if (ASCII::is_ascii($needle . $haystack)) {
9629
            return \stristr($haystack, $needle, $before_needle);
9630
        }
9631
9632
        \preg_match('/^(.*?)' . \preg_quote($needle, '/') . '/usi', $haystack, $match);
9633
9634
        if (!isset($match[1])) {
9635
            return false;
9636
        }
9637
9638
        if ($before_needle) {
9639
            return $match[1];
9640
        }
9641
9642
        return self::substr($haystack, (int) self::strlen($match[1], $encoding), null, $encoding);
9643
    }
9644
9645
    /**
9646
     * Get the string length, not the byte-length!
9647
     *
9648
     * @see http://php.net/manual/en/function.mb-strlen.php
9649
     *
9650
     * @param string $str        <p>The string being checked for length.</p>
9651
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
9652
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
9653
     *
9654
     * @psalm-pure
9655
     *
9656
     * @return false|int
9657
     *                   <p>
9658
     *                   The number <strong>(int)</strong> of characters in the string $str having character encoding
9659
     *                   $encoding.
9660
     *                   (One multi-byte character counted as +1).
9661
     *                   <br>
9662
     *                   Can return <strong>false</strong>, if e.g. mbstring is not installed and we process invalid
9663
     *                   chars.
9664
     *                   </p>
9665
     */
9666 174
    public static function strlen(
9667
        string $str,
9668
        string $encoding = 'UTF-8',
9669
        bool $clean_utf8 = false
9670
    ) {
9671 174
        if ($str === '') {
9672 21
            return 0;
9673
        }
9674
9675 172
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9676 12
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9677
        }
9678
9679 172
        if ($clean_utf8) {
9680
            // "mb_strlen" and "\iconv_strlen" returns wrong length,
9681
            // if invalid characters are found in $str
9682 4
            $str = self::clean($str);
9683
        }
9684
9685
        //
9686
        // fallback via mbstring
9687
        //
9688
9689 172
        if (self::$SUPPORT['mbstring'] === true) {
9690 166
            if ($encoding === 'UTF-8') {
9691 166
                return \mb_strlen($str);
9692
            }
9693
9694 4
            return \mb_strlen($str, $encoding);
9695
        }
9696
9697
        //
9698
        // fallback for binary || ascii only
9699
        //
9700
9701
        if (
9702 8
            $encoding === 'CP850'
9703
            ||
9704 8
            $encoding === 'ASCII'
9705
        ) {
9706
            return \strlen($str);
9707
        }
9708
9709
        if (
9710 8
            $encoding !== 'UTF-8'
9711
            &&
9712 8
            self::$SUPPORT['mbstring'] === false
9713
            &&
9714 8
            self::$SUPPORT['iconv'] === false
9715
        ) {
9716
            /**
9717
             * @psalm-suppress ImpureFunctionCall - is is only a warning
9718
             */
9719 2
            \trigger_error('UTF8::strlen() without mbstring / iconv cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
9720
        }
9721
9722
        //
9723
        // fallback via iconv
9724
        //
9725
9726 8
        if (self::$SUPPORT['iconv'] === true) {
9727
            $return_tmp = \iconv_strlen($str, $encoding);
9728
            if ($return_tmp !== false) {
9729
                return $return_tmp;
9730
            }
9731
        }
9732
9733
        //
9734
        // fallback via intl
9735
        //
9736
9737
        if (
9738 8
            $encoding === 'UTF-8' // INFO: "grapheme_strlen()" can't handle other encodings
9739
            &&
9740 8
            self::$SUPPORT['intl'] === true
9741
        ) {
9742
            $return_tmp = \grapheme_strlen($str);
9743
            if ($return_tmp !== null) {
9744
                return $return_tmp;
9745
            }
9746
        }
9747
9748
        //
9749
        // fallback for ascii only
9750
        //
9751
9752 8
        if (ASCII::is_ascii($str)) {
9753 4
            return \strlen($str);
9754
        }
9755
9756
        //
9757
        // fallback via vanilla php
9758
        //
9759
9760 8
        \preg_match_all('/./us', $str, $parts);
9761
9762 8
        $return_tmp = \count($parts[0]);
9763 8
        if ($return_tmp === 0) {
9764
            return false;
9765
        }
9766
9767 8
        return $return_tmp;
9768
    }
9769
9770
    /**
9771
     * Get string length in byte.
9772
     *
9773
     * @param string $str
9774
     *
9775
     * @psalm-pure
9776
     *
9777
     * @return int
9778
     */
9779 1
    public static function strlen_in_byte(string $str): int
9780
    {
9781 1
        if ($str === '') {
9782
            return 0;
9783
        }
9784
9785 1
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
9786
            // "mb_" is available if overload is used, so use it ...
9787
            return \mb_strlen($str, 'CP850'); // 8-BIT
9788
        }
9789
9790 1
        return \strlen($str);
9791
    }
9792
9793
    /**
9794
     * Case-insensitive string comparisons using a "natural order" algorithm.
9795
     *
9796
     * INFO: natural order version of UTF8::strcasecmp()
9797
     *
9798
     * @param string $str1     <p>The first string.</p>
9799
     * @param string $str2     <p>The second string.</p>
9800
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
9801
     *
9802
     * @psalm-pure
9803
     *
9804
     * @return int
9805
     *             <strong>&lt; 0</strong> if str1 is less than str2<br>
9806
     *             <strong>&gt; 0</strong> if str1 is greater than str2<br>
9807
     *             <strong>0</strong> if they are equal
9808
     */
9809 2
    public static function strnatcasecmp(string $str1, string $str2, string $encoding = 'UTF-8'): int
9810
    {
9811 2
        return self::strnatcmp(
9812 2
            self::strtocasefold($str1, true, false, $encoding, null, false),
9813 2
            self::strtocasefold($str2, true, false, $encoding, null, false)
9814
        );
9815
    }
9816
9817
    /**
9818
     * String comparisons using a "natural order" algorithm
9819
     *
9820
     * INFO: natural order version of UTF8::strcmp()
9821
     *
9822
     * @see http://php.net/manual/en/function.strnatcmp.php
9823
     *
9824
     * @param string $str1 <p>The first string.</p>
9825
     * @param string $str2 <p>The second string.</p>
9826
     *
9827
     * @psalm-pure
9828
     *
9829
     * @return int
9830
     *             <strong>&lt; 0</strong> if str1 is less than str2;<br>
9831
     *             <strong>&gt; 0</strong> if str1 is greater than str2;<br>
9832
     *             <strong>0</strong> if they are equal
9833
     */
9834 4
    public static function strnatcmp(string $str1, string $str2): int
9835
    {
9836 4
        if ($str1 === $str2) {
9837 4
            return 0;
9838
        }
9839
9840 4
        return \strnatcmp(
9841 4
            (string) self::strtonatfold($str1),
9842 4
            (string) self::strtonatfold($str2)
9843
        );
9844
    }
9845
9846
    /**
9847
     * Case-insensitive string comparison of the first n characters.
9848
     *
9849
     * @see http://php.net/manual/en/function.strncasecmp.php
9850
     *
9851
     * @param string $str1     <p>The first string.</p>
9852
     * @param string $str2     <p>The second string.</p>
9853
     * @param int    $len      <p>The length of strings to be used in the comparison.</p>
9854
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
9855
     *
9856
     * @psalm-pure
9857
     *
9858
     * @return int
9859
     *             <strong>&lt; 0</strong> if <i>str1</i> is less than <i>str2</i>;<br>
9860
     *             <strong>&gt; 0</strong> if <i>str1</i> is greater than <i>str2</i>;<br>
9861
     *             <strong>0</strong> if they are equal
9862
     */
9863 2
    public static function strncasecmp(
9864
        string $str1,
9865
        string $str2,
9866
        int $len,
9867
        string $encoding = 'UTF-8'
9868
    ): int {
9869 2
        return self::strncmp(
9870 2
            self::strtocasefold($str1, true, false, $encoding, null, false),
9871 2
            self::strtocasefold($str2, true, false, $encoding, null, false),
9872 2
            $len
9873
        );
9874
    }
9875
9876
    /**
9877
     * String comparison of the first n characters.
9878
     *
9879
     * @see http://php.net/manual/en/function.strncmp.php
9880
     *
9881
     * @param string $str1     <p>The first string.</p>
9882
     * @param string $str2     <p>The second string.</p>
9883
     * @param int    $len      <p>Number of characters to use in the comparison.</p>
9884
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
9885
     *
9886
     * @psalm-pure
9887
     *
9888
     * @return int
9889
     *             <strong>&lt; 0</strong> if <i>str1</i> is less than <i>str2</i>;<br>
9890
     *             <strong>&gt; 0</strong> if <i>str1</i> is greater than <i>str2</i>;<br>
9891
     *             <strong>0</strong> if they are equal
9892
     */
9893 4
    public static function strncmp(
9894
        string $str1,
9895
        string $str2,
9896
        int $len,
9897
        string $encoding = 'UTF-8'
9898
    ): int {
9899 4
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9900
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9901
        }
9902
9903 4
        if ($encoding === 'UTF-8') {
9904 4
            $str1 = (string) \mb_substr($str1, 0, $len);
9905 4
            $str2 = (string) \mb_substr($str2, 0, $len);
9906
        } else {
9907
            $str1 = (string) self::substr($str1, 0, $len, $encoding);
9908
            $str2 = (string) self::substr($str2, 0, $len, $encoding);
9909
        }
9910
9911 4
        return self::strcmp($str1, $str2);
9912
    }
9913
9914
    /**
9915
     * Search a string for any of a set of characters.
9916
     *
9917
     * @see http://php.net/manual/en/function.strpbrk.php
9918
     *
9919
     * @param string $haystack  <p>The string where char_list is looked for.</p>
9920
     * @param string $char_list <p>This parameter is case-sensitive.</p>
9921
     *
9922
     * @psalm-pure
9923
     *
9924
     * @return false|string
9925
     *                      <p>The string starting from the character found, or false if it is not found.</p>
9926
     */
9927 2
    public static function strpbrk(string $haystack, string $char_list)
9928
    {
9929 2
        if ($haystack === '' || $char_list === '') {
9930 2
            return false;
9931
        }
9932
9933 2
        if (\preg_match('/' . self::rxClass($char_list) . '/us', $haystack, $m)) {
9934 2
            return \substr($haystack, (int) \strpos($haystack, $m[0]));
9935
        }
9936
9937 2
        return false;
9938
    }
9939
9940
    /**
9941
     * Find the position of the first occurrence of a substring in a string.
9942
     *
9943
     * @see http://php.net/manual/en/function.mb-strpos.php
9944
     *
9945
     * @param string     $haystack   <p>The string from which to get the position of the first occurrence of needle.</p>
9946
     * @param int|string $needle     <p>The string to find in haystack.<br>Or a code point as int.</p>
9947
     * @param int        $offset     [optional] <p>The search offset. If it is not specified, 0 is used.</p>
9948
     * @param string     $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
9949
     * @param bool       $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
9950
     *
9951
     * @psalm-pure
9952
     *
9953
     * @return false|int
9954
     *                   The <strong>(int)</strong> numeric position of the first occurrence of needle in the haystack
9955
     *                   string.<br> If needle is not found it returns false.
9956
     */
9957 53
    public static function strpos(
9958
        string $haystack,
9959
        $needle,
9960
        int $offset = 0,
9961
        string $encoding = 'UTF-8',
9962
        bool $clean_utf8 = false
9963
    ) {
9964 53
        if ($haystack === '') {
9965 4
            return false;
9966
        }
9967
9968
        // iconv and mbstring do not support integer $needle
9969 52
        if ((int) $needle === $needle) {
9970
            $needle = (string) self::chr($needle);
9971
        }
9972 52
        $needle = (string) $needle;
9973
9974 52
        if ($needle === '') {
9975 2
            return false;
9976
        }
9977
9978 52
        if ($clean_utf8) {
9979
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
9980
            // if invalid characters are found in $haystack before $needle
9981 3
            $needle = self::clean($needle);
9982 3
            $haystack = self::clean($haystack);
9983
        }
9984
9985 52
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9986 10
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9987
        }
9988
9989
        //
9990
        // fallback via mbstring
9991
        //
9992
9993 52
        if (self::$SUPPORT['mbstring'] === true) {
9994 50
            if ($encoding === 'UTF-8') {
9995 50
                return \mb_strpos($haystack, $needle, $offset);
9996
            }
9997
9998 2
            return \mb_strpos($haystack, $needle, $offset, $encoding);
9999
        }
10000
10001
        //
10002
        // fallback for binary || ascii only
10003
        //
10004
        if (
10005 4
            $encoding === 'CP850'
10006
            ||
10007 4
            $encoding === 'ASCII'
10008
        ) {
10009 2
            return \strpos($haystack, $needle, $offset);
10010
        }
10011
10012
        if (
10013 4
            $encoding !== 'UTF-8'
10014
            &&
10015 4
            self::$SUPPORT['iconv'] === false
10016
            &&
10017 4
            self::$SUPPORT['mbstring'] === false
10018
        ) {
10019
            /**
10020
             * @psalm-suppress ImpureFunctionCall - is is only a warning
10021
             */
10022 2
            \trigger_error('UTF8::strpos() without mbstring / iconv cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
10023
        }
10024
10025
        //
10026
        // fallback via intl
10027
        //
10028
10029
        if (
10030 4
            $encoding === 'UTF-8' // INFO: "grapheme_strpos()" can't handle other encodings
10031
            &&
10032 4
            $offset >= 0 // grapheme_strpos() can't handle negative offset
10033
            &&
10034 4
            self::$SUPPORT['intl'] === true
10035
        ) {
10036
            $return_tmp = \grapheme_strpos($haystack, $needle, $offset);
10037
            if ($return_tmp !== false) {
10038
                return $return_tmp;
10039
            }
10040
        }
10041
10042
        //
10043
        // fallback via iconv
10044
        //
10045
10046
        if (
10047 4
            $offset >= 0 // iconv_strpos() can't handle negative offset
10048
            &&
10049 4
            self::$SUPPORT['iconv'] === true
10050
        ) {
10051
            // ignore invalid negative offset to keep compatibility
10052
            // with php < 5.5.35, < 5.6.21, < 7.0.6
10053
            $return_tmp = \iconv_strpos($haystack, $needle, $offset > 0 ? $offset : 0, $encoding);
10054
            if ($return_tmp !== false) {
10055
                return $return_tmp;
10056
            }
10057
        }
10058
10059
        //
10060
        // fallback for ascii only
10061
        //
10062
10063 4
        if (ASCII::is_ascii($haystack . $needle)) {
10064 2
            return \strpos($haystack, $needle, $offset);
10065
        }
10066
10067
        //
10068
        // fallback via vanilla php
10069
        //
10070
10071 4
        $haystack_tmp = self::substr($haystack, $offset, null, $encoding);
10072 4
        if ($haystack_tmp === false) {
10073
            $haystack_tmp = '';
10074
        }
10075 4
        $haystack = (string) $haystack_tmp;
10076
10077 4
        if ($offset < 0) {
10078
            $offset = 0;
10079
        }
10080
10081 4
        $pos = \strpos($haystack, $needle);
10082 4
        if ($pos === false) {
10083 2
            return false;
10084
        }
10085
10086 4
        if ($pos) {
10087 4
            return $offset + (int) self::strlen(\substr($haystack, 0, $pos), $encoding);
10088
        }
10089
10090 2
        return $offset + 0;
10091
    }
10092
10093
    /**
10094
     * Find the position of the first occurrence of a substring in a string.
10095
     *
10096
     * @param string $haystack <p>
10097
     *                         The string being checked.
10098
     *                         </p>
10099
     * @param string $needle   <p>
10100
     *                         The position counted from the beginning of haystack.
10101
     *                         </p>
10102
     * @param int    $offset   [optional] <p>
10103
     *                         The search offset. If it is not specified, 0 is used.
10104
     *                         </p>
10105
     *
10106
     * @psalm-pure
10107
     *
10108
     * @return false|int
10109
     *                   <p>The numeric position of the first occurrence of needle in the
10110
     *                   haystack string. If needle is not found, it returns false.</p>
10111
     */
10112 2
    public static function strpos_in_byte(string $haystack, string $needle, int $offset = 0)
10113
    {
10114 2
        if ($haystack === '' || $needle === '') {
10115
            return false;
10116
        }
10117
10118 2
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
10119
            // "mb_" is available if overload is used, so use it ...
10120
            return \mb_strpos($haystack, $needle, $offset, 'CP850'); // 8-BIT
10121
        }
10122
10123 2
        return \strpos($haystack, $needle, $offset);
10124
    }
10125
10126
    /**
10127
     * Find the position of the first occurrence of a substring in a string, case-insensitive.
10128
     *
10129
     * @param string $haystack <p>
10130
     *                         The string being checked.
10131
     *                         </p>
10132
     * @param string $needle   <p>
10133
     *                         The position counted from the beginning of haystack.
10134
     *                         </p>
10135
     * @param int    $offset   [optional] <p>
10136
     *                         The search offset. If it is not specified, 0 is used.
10137
     *                         </p>
10138
     *
10139
     * @psalm-pure
10140
     *
10141
     * @return false|int
10142
     *                   <p>The numeric position of the first occurrence of needle in the
10143
     *                   haystack string. If needle is not found, it returns false.</p>
10144
     */
10145 2
    public static function stripos_in_byte(string $haystack, string $needle, int $offset = 0)
10146
    {
10147 2
        if ($haystack === '' || $needle === '') {
10148
            return false;
10149
        }
10150
10151 2
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
10152
            // "mb_" is available if overload is used, so use it ...
10153
            return \mb_stripos($haystack, $needle, $offset, 'CP850'); // 8-BIT
10154
        }
10155
10156 2
        return \stripos($haystack, $needle, $offset);
10157
    }
10158
10159
    /**
10160
     * Find the last occurrence of a character in a string within another.
10161
     *
10162
     * @see http://php.net/manual/en/function.mb-strrchr.php
10163
     *
10164
     * @param string $haystack      <p>The string from which to get the last occurrence of needle.</p>
10165
     * @param string $needle        <p>The string to find in haystack</p>
10166
     * @param bool   $before_needle [optional] <p>
10167
     *                              Determines which portion of haystack
10168
     *                              this function returns.
10169
     *                              If set to true, it returns all of haystack
10170
     *                              from the beginning to the last occurrence of needle.
10171
     *                              If set to false, it returns all of haystack
10172
     *                              from the last occurrence of needle to the end,
10173
     *                              </p>
10174
     * @param string $encoding      [optional] <p>Set the charset for e.g. "mb_" function</p>
10175
     * @param bool   $clean_utf8    [optional] <p>Remove non UTF-8 chars from the string.</p>
10176
     *
10177
     * @psalm-pure
10178
     *
10179
     * @return false|string
10180
     *                      <p>The portion of haystack or false if needle is not found.</p>
10181
     */
10182 2
    public static function strrchr(
10183
        string $haystack,
10184
        string $needle,
10185
        bool $before_needle = false,
10186
        string $encoding = 'UTF-8',
10187
        bool $clean_utf8 = false
10188
    ) {
10189 2
        if ($haystack === '' || $needle === '') {
10190 2
            return false;
10191
        }
10192
10193 2
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
10194 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
10195
        }
10196
10197 2
        if ($clean_utf8) {
10198
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
10199
            // if invalid characters are found in $haystack before $needle
10200 2
            $needle = self::clean($needle);
10201 2
            $haystack = self::clean($haystack);
10202
        }
10203
10204
        //
10205
        // fallback via mbstring
10206
        //
10207
10208 2
        if (self::$SUPPORT['mbstring'] === true) {
10209 2
            if ($encoding === 'UTF-8') {
10210 2
                return \mb_strrchr($haystack, $needle, $before_needle);
10211
            }
10212
10213 2
            return \mb_strrchr($haystack, $needle, $before_needle, $encoding);
10214
        }
10215
10216
        //
10217
        // fallback for binary || ascii only
10218
        //
10219
10220
        if (
10221
            !$before_needle
10222
            &&
10223
            (
10224
                $encoding === 'CP850'
10225
                ||
10226
                $encoding === 'ASCII'
10227
            )
10228
        ) {
10229
            return \strrchr($haystack, $needle);
10230
        }
10231
10232
        if (
10233
            $encoding !== 'UTF-8'
10234
            &&
10235
            self::$SUPPORT['mbstring'] === false
10236
        ) {
10237
            /**
10238
             * @psalm-suppress ImpureFunctionCall - is is only a warning
10239
             */
10240
            \trigger_error('UTF8::strrchr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
10241
        }
10242
10243
        //
10244
        // fallback via iconv
10245
        //
10246
10247
        if (self::$SUPPORT['iconv'] === true) {
10248
            $needle_tmp = self::substr($needle, 0, 1, $encoding);
10249
            if ($needle_tmp === false) {
10250
                return false;
10251
            }
10252
            $needle = (string) $needle_tmp;
10253
10254
            $pos = \iconv_strrpos($haystack, $needle, $encoding);
10255
            if ($pos === false) {
10256
                return false;
10257
            }
10258
10259
            if ($before_needle) {
10260
                return self::substr($haystack, 0, $pos, $encoding);
10261
            }
10262
10263
            return self::substr($haystack, $pos, null, $encoding);
10264
        }
10265
10266
        //
10267
        // fallback via vanilla php
10268
        //
10269
10270
        $needle_tmp = self::substr($needle, 0, 1, $encoding);
10271
        if ($needle_tmp === false) {
10272
            return false;
10273
        }
10274
        $needle = (string) $needle_tmp;
10275
10276
        $pos = self::strrpos($haystack, $needle, 0, $encoding);
10277
        if ($pos === false) {
10278
            return false;
10279
        }
10280
10281
        if ($before_needle) {
10282
            return self::substr($haystack, 0, $pos, $encoding);
10283
        }
10284
10285
        return self::substr($haystack, $pos, null, $encoding);
10286
    }
10287
10288
    /**
10289
     * Reverses characters order in the string.
10290
     *
10291
     * @param string $str      <p>The input string.</p>
10292
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
10293
     *
10294
     * @psalm-pure
10295
     *
10296
     * @return string
10297
     *                <p>The string with characters in the reverse sequence.</p>
10298
     */
10299 10
    public static function strrev(string $str, string $encoding = 'UTF-8'): string
10300
    {
10301 10
        if ($str === '') {
10302 4
            return '';
10303
        }
10304
10305
        // init
10306 8
        $reversed = '';
10307
10308 8
        $str = self::emoji_encode($str, true);
10309
10310 8
        if ($encoding === 'UTF-8') {
10311 8
            if (self::$SUPPORT['intl'] === true) {
10312
                // try "grapheme" first: https://stackoverflow.com/questions/17496493/strrev-dosent-support-utf-8
10313 8
                $i = (int) \grapheme_strlen($str);
10314 8
                while ($i--) {
10315 8
                    $reversed_tmp = \grapheme_substr($str, $i, 1);
10316 8
                    if ($reversed_tmp !== false) {
10317 8
                        $reversed .= $reversed_tmp;
10318
                    }
10319
                }
10320
            } else {
10321
                $i = (int) \mb_strlen($str);
10322 8
                while ($i--) {
10323
                    $reversed_tmp = \mb_substr($str, $i, 1);
10324
                    if ($reversed_tmp !== false) {
10325
                        $reversed .= $reversed_tmp;
10326
                    }
10327
                }
10328
            }
10329
        } else {
10330
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
10331
10332
            $i = (int) self::strlen($str, $encoding);
10333
            while ($i--) {
10334
                $reversed_tmp = self::substr($str, $i, 1, $encoding);
10335
                if ($reversed_tmp !== false) {
10336
                    $reversed .= $reversed_tmp;
10337
                }
10338
            }
10339
        }
10340
10341 8
        return self::emoji_decode($reversed, true);
10342
    }
10343
10344
    /**
10345
     * Find the last occurrence of a character in a string within another, case-insensitive.
10346
     *
10347
     * @see http://php.net/manual/en/function.mb-strrichr.php
10348
     *
10349
     * @param string $haystack      <p>The string from which to get the last occurrence of needle.</p>
10350
     * @param string $needle        <p>The string to find in haystack.</p>
10351
     * @param bool   $before_needle [optional] <p>
10352
     *                              Determines which portion of haystack
10353
     *                              this function returns.
10354
     *                              If set to true, it returns all of haystack
10355
     *                              from the beginning to the last occurrence of needle.
10356
     *                              If set to false, it returns all of haystack
10357
     *                              from the last occurrence of needle to the end,
10358
     *                              </p>
10359
     * @param string $encoding      [optional] <p>Set the charset for e.g. "mb_" function</p>
10360
     * @param bool   $clean_utf8    [optional] <p>Remove non UTF-8 chars from the string.</p>
10361
     *
10362
     * @psalm-pure
10363
     *
10364
     * @return false|string
10365
     *                      <p>The portion of haystack or<br>false if needle is not found.</p>
10366
     */
10367 3
    public static function strrichr(
10368
        string $haystack,
10369
        string $needle,
10370
        bool $before_needle = false,
10371
        string $encoding = 'UTF-8',
10372
        bool $clean_utf8 = false
10373
    ) {
10374 3
        if ($haystack === '' || $needle === '') {
10375 2
            return false;
10376
        }
10377
10378 3
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
10379 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
10380
        }
10381
10382 3
        if ($clean_utf8) {
10383
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
10384
            // if invalid characters are found in $haystack before $needle
10385 2
            $needle = self::clean($needle);
10386 2
            $haystack = self::clean($haystack);
10387
        }
10388
10389
        //
10390
        // fallback via mbstring
10391
        //
10392
10393 3
        if (self::$SUPPORT['mbstring'] === true) {
10394 3
            if ($encoding === 'UTF-8') {
10395 3
                return \mb_strrichr($haystack, $needle, $before_needle);
10396
            }
10397
10398 2
            return \mb_strrichr($haystack, $needle, $before_needle, $encoding);
10399
        }
10400
10401
        //
10402
        // fallback via vanilla php
10403
        //
10404
10405
        $needle_tmp = self::substr($needle, 0, 1, $encoding);
10406
        if ($needle_tmp === false) {
10407
            return false;
10408
        }
10409
        $needle = (string) $needle_tmp;
10410
10411
        $pos = self::strripos($haystack, $needle, 0, $encoding);
10412
        if ($pos === false) {
10413
            return false;
10414
        }
10415
10416
        if ($before_needle) {
10417
            return self::substr($haystack, 0, $pos, $encoding);
10418
        }
10419
10420
        return self::substr($haystack, $pos, null, $encoding);
10421
    }
10422
10423
    /**
10424
     * Find the position of the last occurrence of a substring in a string, case-insensitive.
10425
     *
10426
     * @param string     $haystack   <p>The string to look in.</p>
10427
     * @param int|string $needle     <p>The string to look for.</p>
10428
     * @param int        $offset     [optional] <p>Number of characters to ignore in the beginning or end.</p>
10429
     * @param string     $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
10430
     * @param bool       $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
10431
     *
10432
     * @psalm-pure
10433
     *
10434
     * @return false|int
10435
     *                   <p>The <strong>(int)</strong> numeric position of the last occurrence of needle in the haystack
10436
     *                   string.<br>If needle is not found, it returns false.</p>
10437
     */
10438 14
    public static function strripos(
10439
        string $haystack,
10440
        $needle,
10441
        int $offset = 0,
10442
        string $encoding = 'UTF-8',
10443
        bool $clean_utf8 = false
10444
    ) {
10445 14
        if ($haystack === '') {
10446
            return false;
10447
        }
10448
10449
        // iconv and mbstring do not support integer $needle
10450 14
        if ((int) $needle === $needle && $needle >= 0) {
10451
            $needle = (string) self::chr($needle);
10452
        }
10453 14
        $needle = (string) $needle;
10454
10455 14
        if ($needle === '') {
10456
            return false;
10457
        }
10458
10459 14
        if ($clean_utf8) {
10460
            // mb_strripos() && iconv_strripos() is not tolerant to invalid characters
10461 3
            $needle = self::clean($needle);
10462 3
            $haystack = self::clean($haystack);
10463
        }
10464
10465 14
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
10466 9
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
10467
        }
10468
10469
        //
10470
        // fallback via mbstrig
10471
        //
10472
10473 14
        if (self::$SUPPORT['mbstring'] === true) {
10474 14
            if ($encoding === 'UTF-8') {
10475 14
                return \mb_strripos($haystack, $needle, $offset);
10476
            }
10477
10478
            return \mb_strripos($haystack, $needle, $offset, $encoding);
10479
        }
10480
10481
        //
10482
        // fallback for binary || ascii only
10483
        //
10484
10485
        if (
10486
            $encoding === 'CP850'
10487
            ||
10488
            $encoding === 'ASCII'
10489
        ) {
10490
            return \strripos($haystack, $needle, $offset);
10491
        }
10492
10493
        if (
10494
            $encoding !== 'UTF-8'
10495
            &&
10496
            self::$SUPPORT['mbstring'] === false
10497
        ) {
10498
            /**
10499
             * @psalm-suppress ImpureFunctionCall - is is only a warning
10500
             */
10501
            \trigger_error('UTF8::strripos() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
10502
        }
10503
10504
        //
10505
        // fallback via intl
10506
        //
10507
10508
        if (
10509
            $encoding === 'UTF-8' // INFO: "grapheme_strripos()" can't handle other encodings
10510
            &&
10511
            $offset >= 0 // grapheme_strripos() can't handle negative offset
10512
            &&
10513
            self::$SUPPORT['intl'] === true
10514
        ) {
10515
            $return_tmp = \grapheme_strripos($haystack, $needle, $offset);
10516
            if ($return_tmp !== false) {
10517
                return $return_tmp;
10518
            }
10519
        }
10520
10521
        //
10522
        // fallback for ascii only
10523
        //
10524
10525
        if (ASCII::is_ascii($haystack . $needle)) {
10526
            return \strripos($haystack, $needle, $offset);
10527
        }
10528
10529
        //
10530
        // fallback via vanilla php
10531
        //
10532
10533
        $haystack = self::strtocasefold($haystack, true, false, $encoding);
10534
        $needle = self::strtocasefold($needle, true, false, $encoding);
10535
10536
        return self::strrpos($haystack, $needle, $offset, $encoding, $clean_utf8);
10537
    }
10538
10539
    /**
10540
     * Finds position of last occurrence of a string within another, case-insensitive.
10541
     *
10542
     * @param string $haystack <p>
10543
     *                         The string from which to get the position of the last occurrence
10544
     *                         of needle.
10545
     *                         </p>
10546
     * @param string $needle   <p>
10547
     *                         The string to find in haystack.
10548
     *                         </p>
10549
     * @param int    $offset   [optional] <p>
10550
     *                         The position in haystack
10551
     *                         to start searching.
10552
     *                         </p>
10553
     *
10554
     * @psalm-pure
10555
     *
10556
     * @return false|int
10557
     *                   <p>eturn the numeric position of the last occurrence of needle in the
10558
     *                   haystack string, or false if needle is not found.</p>
10559
     */
10560 2
    public static function strripos_in_byte(string $haystack, string $needle, int $offset = 0)
10561
    {
10562 2
        if ($haystack === '' || $needle === '') {
10563
            return false;
10564
        }
10565
10566 2
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
10567
            // "mb_" is available if overload is used, so use it ...
10568
            return \mb_strripos($haystack, $needle, $offset, 'CP850'); // 8-BIT
10569
        }
10570
10571 2
        return \strripos($haystack, $needle, $offset);
10572
    }
10573
10574
    /**
10575
     * Find the position of the last occurrence of a substring in a string.
10576
     *
10577
     * @see http://php.net/manual/en/function.mb-strrpos.php
10578
     *
10579
     * @param string     $haystack   <p>The string being checked, for the last occurrence of needle</p>
10580
     * @param int|string $needle     <p>The string to find in haystack.<br>Or a code point as int.</p>
10581
     * @param int        $offset     [optional] <p>May be specified to begin searching an arbitrary number of characters
10582
     *                               into the string. Negative values will stop searching at an arbitrary point prior to
10583
     *                               the end of the string.
10584
     *                               </p>
10585
     * @param string     $encoding   [optional] <p>Set the charset.</p>
10586
     * @param bool       $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
10587
     *
10588
     * @psalm-pure
10589
     *
10590
     * @return false|int
10591
     *                   <p>The <strong>(int)</strong> numeric position of the last occurrence of needle in the haystack
10592
     *                   string.<br>If needle is not found, it returns false.</p>
10593
     */
10594 35
    public static function strrpos(
10595
        string $haystack,
10596
        $needle,
10597
        int $offset = 0,
10598
        string $encoding = 'UTF-8',
10599
        bool $clean_utf8 = false
10600
    ) {
10601 35
        if ($haystack === '') {
10602 3
            return false;
10603
        }
10604
10605
        // iconv and mbstring do not support integer $needle
10606 34
        if ((int) $needle === $needle && $needle >= 0) {
10607 2
            $needle = (string) self::chr($needle);
10608
        }
10609 34
        $needle = (string) $needle;
10610
10611 34
        if ($needle === '') {
10612 2
            return false;
10613
        }
10614
10615 34
        if ($clean_utf8) {
10616
            // mb_strrpos && iconv_strrpos is not tolerant to invalid characters
10617 4
            $needle = self::clean($needle);
10618 4
            $haystack = self::clean($haystack);
10619
        }
10620
10621 34
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
10622 8
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
10623
        }
10624
10625
        //
10626
        // fallback via mbstring
10627
        //
10628
10629 34
        if (self::$SUPPORT['mbstring'] === true) {
10630 34
            if ($encoding === 'UTF-8') {
10631 34
                return \mb_strrpos($haystack, $needle, $offset);
10632
            }
10633
10634 2
            return \mb_strrpos($haystack, $needle, $offset, $encoding);
10635
        }
10636
10637
        //
10638
        // fallback for binary || ascii only
10639
        //
10640
10641
        if (
10642
            $encoding === 'CP850'
10643
            ||
10644
            $encoding === 'ASCII'
10645
        ) {
10646
            return \strrpos($haystack, $needle, $offset);
10647
        }
10648
10649
        if (
10650
            $encoding !== 'UTF-8'
10651
            &&
10652
            self::$SUPPORT['mbstring'] === false
10653
        ) {
10654
            /**
10655
             * @psalm-suppress ImpureFunctionCall - is is only a warning
10656
             */
10657
            \trigger_error('UTF8::strrpos() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
10658
        }
10659
10660
        //
10661
        // fallback via intl
10662
        //
10663
10664
        if (
10665
            $offset >= 0 // grapheme_strrpos() can't handle negative offset
10666
            &&
10667
            $encoding === 'UTF-8' // INFO: "grapheme_strrpos()" can't handle other encodings
10668
            &&
10669
            self::$SUPPORT['intl'] === true
10670
        ) {
10671
            $return_tmp = \grapheme_strrpos($haystack, $needle, $offset);
10672
            if ($return_tmp !== false) {
10673
                return $return_tmp;
10674
            }
10675
        }
10676
10677
        //
10678
        // fallback for ascii only
10679
        //
10680
10681
        if (ASCII::is_ascii($haystack . $needle)) {
10682
            return \strrpos($haystack, $needle, $offset);
10683
        }
10684
10685
        //
10686
        // fallback via vanilla php
10687
        //
10688
10689
        $haystack_tmp = null;
10690
        if ($offset > 0) {
10691
            $haystack_tmp = self::substr($haystack, $offset);
10692
        } elseif ($offset < 0) {
10693
            $haystack_tmp = self::substr($haystack, 0, $offset);
10694
            $offset = 0;
10695
        }
10696
10697
        if ($haystack_tmp !== null) {
10698
            if ($haystack_tmp === false) {
10699
                $haystack_tmp = '';
10700
            }
10701
            $haystack = (string) $haystack_tmp;
10702
        }
10703
10704
        $pos = \strrpos($haystack, $needle);
10705
        if ($pos === false) {
10706
            return false;
10707
        }
10708
10709
        /** @var false|string $str_tmp - needed for PhpStan (stubs error) */
10710
        $str_tmp = \substr($haystack, 0, $pos);
10711
        if ($str_tmp === false) {
10712
            return false;
10713
        }
10714
10715
        return $offset + (int) self::strlen($str_tmp);
10716
    }
10717
10718
    /**
10719
     * Find the position of the last occurrence of a substring in a string.
10720
     *
10721
     * @param string $haystack <p>
10722
     *                         The string being checked, for the last occurrence
10723
     *                         of needle.
10724
     *                         </p>
10725
     * @param string $needle   <p>
10726
     *                         The string to find in haystack.
10727
     *                         </p>
10728
     * @param int    $offset   [optional] <p>May be specified to begin searching an arbitrary number of characters into
10729
     *                         the string. Negative values will stop searching at an arbitrary point
10730
     *                         prior to the end of the string.
10731
     *                         </p>
10732
     *
10733
     * @psalm-pure
10734
     *
10735
     * @return false|int
10736
     *                   <p>The numeric position of the last occurrence of needle in the
10737
     *                   haystack string. If needle is not found, it returns false.</p>
10738
     */
10739 2
    public static function strrpos_in_byte(string $haystack, string $needle, int $offset = 0)
10740
    {
10741 2
        if ($haystack === '' || $needle === '') {
10742
            return false;
10743
        }
10744
10745 2
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
10746
            // "mb_" is available if overload is used, so use it ...
10747
            return \mb_strrpos($haystack, $needle, $offset, 'CP850'); // 8-BIT
10748
        }
10749
10750 2
        return \strrpos($haystack, $needle, $offset);
10751
    }
10752
10753
    /**
10754
     * Finds the length of the initial segment of a string consisting entirely of characters contained within a given
10755
     * mask.
10756
     *
10757
     * @param string $str      <p>The input string.</p>
10758
     * @param string $mask     <p>The mask of chars</p>
10759
     * @param int    $offset   [optional]
10760
     * @param int    $length   [optional]
10761
     * @param string $encoding [optional] <p>Set the charset.</p>
10762
     *
10763
     * @psalm-pure
10764
     *
10765
     * @return false|int
10766
     */
10767 10
    public static function strspn(
10768
        string $str,
10769
        string $mask,
10770
        int $offset = 0,
10771
        int $length = null,
10772
        string $encoding = 'UTF-8'
10773
    ) {
10774 10
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
10775
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
10776
        }
10777
10778 10
        if ($offset || $length !== null) {
10779 2
            if ($encoding === 'UTF-8') {
10780 2
                if ($length === null) {
10781
                    $str = (string) \mb_substr($str, $offset);
10782
                } else {
10783 2
                    $str = (string) \mb_substr($str, $offset, $length);
10784
                }
10785
            } else {
10786
                $str = (string) self::substr($str, $offset, $length, $encoding);
10787
            }
10788
        }
10789
10790 10
        if ($str === '' || $mask === '') {
10791 2
            return 0;
10792
        }
10793
10794 8
        $matches = [];
10795
10796 8
        return \preg_match('/^' . self::rxClass($mask) . '+/u', $str, $matches) ? (int) self::strlen($matches[0], $encoding) : 0;
10797
    }
10798
10799
    /**
10800
     * Returns part of haystack string from the first occurrence of needle to the end of haystack.
10801
     *
10802
     * @param string $haystack      <p>The input string. Must be valid UTF-8.</p>
10803
     * @param string $needle        <p>The string to look for. Must be valid UTF-8.</p>
10804
     * @param bool   $before_needle [optional] <p>
10805
     *                              If <b>TRUE</b>, strstr() returns the part of the
10806
     *                              haystack before the first occurrence of the needle (excluding the needle).
10807
     *                              </p>
10808
     * @param string $encoding      [optional] <p>Set the charset for e.g. "mb_" function</p>
10809
     * @param bool   $clean_utf8    [optional] <p>Remove non UTF-8 chars from the string.</p>
10810
     *
10811
     * @psalm-pure
10812
     *
10813
     * @return false|string
10814
     *                      A sub-string,<br>or <strong>false</strong> if needle is not found
10815
     */
10816 3
    public static function strstr(
10817
        string $haystack,
10818
        string $needle,
10819
        bool $before_needle = false,
10820
        string $encoding = 'UTF-8',
10821
        bool $clean_utf8 = false
10822
    ) {
10823 3
        if ($haystack === '' || $needle === '') {
10824 2
            return false;
10825
        }
10826
10827 3
        if ($clean_utf8) {
10828
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
10829
            // if invalid characters are found in $haystack before $needle
10830
            $needle = self::clean($needle);
10831
            $haystack = self::clean($haystack);
10832
        }
10833
10834 3
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
10835 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
10836
        }
10837
10838
        //
10839
        // fallback via mbstring
10840
        //
10841
10842 3
        if (self::$SUPPORT['mbstring'] === true) {
10843 3
            if ($encoding === 'UTF-8') {
10844 3
                return \mb_strstr($haystack, $needle, $before_needle);
10845
            }
10846
10847 2
            return \mb_strstr($haystack, $needle, $before_needle, $encoding);
10848
        }
10849
10850
        //
10851
        // fallback for binary || ascii only
10852
        //
10853
10854
        if (
10855
            $encoding === 'CP850'
10856
            ||
10857
            $encoding === 'ASCII'
10858
        ) {
10859
            return \strstr($haystack, $needle, $before_needle);
10860
        }
10861
10862
        if (
10863
            $encoding !== 'UTF-8'
10864
            &&
10865
            self::$SUPPORT['mbstring'] === false
10866
        ) {
10867
            /**
10868
             * @psalm-suppress ImpureFunctionCall - is is only a warning
10869
             */
10870
            \trigger_error('UTF8::strstr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
10871
        }
10872
10873
        //
10874
        // fallback via intl
10875
        //
10876
10877
        if (
10878
            $encoding === 'UTF-8' // INFO: "grapheme_strstr()" can't handle other encodings
10879
            &&
10880
            self::$SUPPORT['intl'] === true
10881
        ) {
10882
            $return_tmp = \grapheme_strstr($haystack, $needle, $before_needle);
10883
            if ($return_tmp !== false) {
10884
                return $return_tmp;
10885
            }
10886
        }
10887
10888
        //
10889
        // fallback for ascii only
10890
        //
10891
10892
        if (ASCII::is_ascii($haystack . $needle)) {
10893
            return \strstr($haystack, $needle, $before_needle);
10894
        }
10895
10896
        //
10897
        // fallback via vanilla php
10898
        //
10899
10900
        \preg_match('/^(.*?)' . \preg_quote($needle, '/') . '/us', $haystack, $match);
10901
10902
        if (!isset($match[1])) {
10903
            return false;
10904
        }
10905
10906
        if ($before_needle) {
10907
            return $match[1];
10908
        }
10909
10910
        return self::substr($haystack, (int) self::strlen($match[1]));
10911
    }
10912
10913
    /**
10914
     * Finds first occurrence of a string within another.
10915
     *
10916
     * @param string $haystack      <p>
10917
     *                              The string from which to get the first occurrence
10918
     *                              of needle.
10919
     *                              </p>
10920
     * @param string $needle        <p>
10921
     *                              The string to find in haystack.
10922
     *                              </p>
10923
     * @param bool   $before_needle [optional] <p>
10924
     *                              Determines which portion of haystack
10925
     *                              this function returns.
10926
     *                              If set to true, it returns all of haystack
10927
     *                              from the beginning to the first occurrence of needle.
10928
     *                              If set to false, it returns all of haystack
10929
     *                              from the first occurrence of needle to the end,
10930
     *                              </p>
10931
     *
10932
     * @psalm-pure
10933
     *
10934
     * @return false|string
10935
     *                      <p>The portion of haystack,
10936
     *                      or false if needle is not found.</p>
10937
     */
10938 2
    public static function strstr_in_byte(
10939
        string $haystack,
10940
        string $needle,
10941
        bool $before_needle = false
10942
    ) {
10943 2
        if ($haystack === '' || $needle === '') {
10944
            return false;
10945
        }
10946
10947 2
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
10948
            // "mb_" is available if overload is used, so use it ...
10949
            return \mb_strstr($haystack, $needle, $before_needle, 'CP850'); // 8-BIT
10950
        }
10951
10952 2
        return \strstr($haystack, $needle, $before_needle);
10953
    }
10954
10955
    /**
10956
     * Unicode transformation for case-less matching.
10957
     *
10958
     * @see http://unicode.org/reports/tr21/tr21-5.html
10959
     *
10960
     * @param string      $str        <p>The input string.</p>
10961
     * @param bool        $full       [optional] <p>
10962
     *                                <b>true</b>, replace full case folding chars (default)<br>
10963
     *                                <b>false</b>, use only limited static array [UTF8::$COMMON_CASE_FOLD]
10964
     *                                </p>
10965
     * @param bool        $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
10966
     * @param string      $encoding   [optional] <p>Set the charset.</p>
10967
     * @param string|null $lang       [optional] <p>Set the language for special cases: az, el, lt, tr</p>
10968
     * @param bool        $lower      [optional] <p>Use lowercase string, otherwise use uppercase string. PS: uppercase
10969
     *                                is for some languages better ...</p>
10970
     *
10971
     * @psalm-pure
10972
     *
10973
     * @return string
10974
     */
10975 32
    public static function strtocasefold(
10976
        string $str,
10977
        bool $full = true,
10978
        bool $clean_utf8 = false,
10979
        string $encoding = 'UTF-8',
10980
        string $lang = null,
10981
        bool $lower = true
10982
    ): string {
10983 32
        if ($str === '') {
10984 5
            return '';
10985
        }
10986
10987 31
        if ($clean_utf8) {
10988
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
10989
            // if invalid characters are found in $haystack before $needle
10990 2
            $str = self::clean($str);
10991
        }
10992
10993 31
        $str = self::fixStrCaseHelper($str, $lower, $full);
10994
10995 31
        if ($lang === null && $encoding === 'UTF-8') {
10996 31
            if ($lower) {
10997 2
                return \mb_strtolower($str);
10998
            }
10999
11000 29
            return \mb_strtoupper($str);
11001
        }
11002
11003 2
        if ($lower) {
11004
            return self::strtolower($str, $encoding, false, $lang);
11005
        }
11006
11007 2
        return self::strtoupper($str, $encoding, false, $lang);
11008
    }
11009
11010
    /**
11011
     * Make a string lowercase.
11012
     *
11013
     * @see http://php.net/manual/en/function.mb-strtolower.php
11014
     *
11015
     * @param string      $str                           <p>The string being lowercased.</p>
11016
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
11017
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
11018
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt,
11019
     *                                                   tr</p>
11020
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ
11021
     *                                                   -> ß</p>
11022
     *
11023
     * @psalm-pure
11024
     *
11025
     * @return string
11026
     *                <p>String with all alphabetic characters converted to lowercase.</p>
11027
     */
11028 73
    public static function strtolower(
11029
        $str,
11030
        string $encoding = 'UTF-8',
11031
        bool $clean_utf8 = false,
11032
        string $lang = null,
11033
        bool $try_to_keep_the_string_length = false
11034
    ): string {
11035
        // init
11036 73
        $str = (string) $str;
11037
11038 73
        if ($str === '') {
11039 1
            return '';
11040
        }
11041
11042 72
        if ($clean_utf8) {
11043
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
11044
            // if invalid characters are found in $haystack before $needle
11045 2
            $str = self::clean($str);
11046
        }
11047
11048
        // hack for old php version or for the polyfill ...
11049 72
        if ($try_to_keep_the_string_length) {
11050
            $str = self::fixStrCaseHelper($str, true);
11051
        }
11052
11053 72
        if ($lang === null && $encoding === 'UTF-8') {
11054 13
            return \mb_strtolower($str);
11055
        }
11056
11057 61
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
11058
11059 61
        if ($lang !== null) {
11060 2
            if (self::$SUPPORT['intl'] === true) {
11061 2
                if (self::$INTL_TRANSLITERATOR_LIST === null) {
11062
                    self::$INTL_TRANSLITERATOR_LIST = self::getData('transliterator_list');
11063
                }
11064
11065 2
                $language_code = $lang . '-Lower';
11066 2
                if (!\in_array($language_code, self::$INTL_TRANSLITERATOR_LIST, true)) {
11067
                    /**
11068
                     * @psalm-suppress ImpureFunctionCall - is is only a warning
11069
                     */
11070
                    \trigger_error('UTF8::strtolower() cannot handle special language: ' . $lang . ' | supported: ' . \print_r(self::$INTL_TRANSLITERATOR_LIST, true), \E_USER_WARNING);
11071
11072
                    $language_code = 'Any-Lower';
11073
                }
11074
11075
                /** @noinspection PhpComposerExtensionStubsInspection */
11076
                /** @noinspection UnnecessaryCastingInspection */
11077 2
                return (string) \transliterator_transliterate($language_code, $str);
11078
            }
11079
11080
            /**
11081
             * @psalm-suppress ImpureFunctionCall - is is only a warning
11082
             */
11083
            \trigger_error('UTF8::strtolower() without intl cannot handle the "lang" parameter: ' . $lang, \E_USER_WARNING);
11084
        }
11085
11086
        // always fallback via symfony polyfill
11087 61
        return \mb_strtolower($str, $encoding);
11088
    }
11089
11090
    /**
11091
     * Make a string uppercase.
11092
     *
11093
     * @see http://php.net/manual/en/function.mb-strtoupper.php
11094
     *
11095
     * @param string      $str                           <p>The string being uppercased.</p>
11096
     * @param string      $encoding                      [optional] <p>Set the charset.</p>
11097
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
11098
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt,
11099
     *                                                   tr</p>
11100
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ
11101
     *                                                   -> ß</p>
11102
     *
11103
     * @psalm-pure
11104
     *
11105
     * @return string
11106
     *                <p>String with all alphabetic characters converted to uppercase.</p>
11107
     */
11108 17
    public static function strtoupper(
11109
        $str,
11110
        string $encoding = 'UTF-8',
11111
        bool $clean_utf8 = false,
11112
        string $lang = null,
11113
        bool $try_to_keep_the_string_length = false
11114
    ): string {
11115
        // init
11116 17
        $str = (string) $str;
11117
11118 17
        if ($str === '') {
11119 1
            return '';
11120
        }
11121
11122 16
        if ($clean_utf8) {
11123
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
11124
            // if invalid characters are found in $haystack before $needle
11125 2
            $str = self::clean($str);
11126
        }
11127
11128
        // hack for old php version or for the polyfill ...
11129 16
        if ($try_to_keep_the_string_length) {
11130 2
            $str = self::fixStrCaseHelper($str, false);
11131
        }
11132
11133 16
        if ($lang === null && $encoding === 'UTF-8') {
11134 8
            return \mb_strtoupper($str);
11135
        }
11136
11137 10
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
11138
11139 10
        if ($lang !== null) {
11140 2
            if (self::$SUPPORT['intl'] === true) {
11141 2
                if (self::$INTL_TRANSLITERATOR_LIST === null) {
11142
                    self::$INTL_TRANSLITERATOR_LIST = self::getData('transliterator_list');
11143
                }
11144
11145 2
                $language_code = $lang . '-Upper';
11146 2
                if (!\in_array($language_code, self::$INTL_TRANSLITERATOR_LIST, true)) {
11147
                    /**
11148
                     * @psalm-suppress ImpureFunctionCall - is is only a warning
11149
                     */
11150
                    \trigger_error('UTF8::strtoupper() without intl for special language: ' . $lang, \E_USER_WARNING);
11151
11152
                    $language_code = 'Any-Upper';
11153
                }
11154
11155
                /** @noinspection PhpComposerExtensionStubsInspection */
11156
                /** @noinspection UnnecessaryCastingInspection */
11157 2
                return (string) \transliterator_transliterate($language_code, $str);
11158
            }
11159
11160
            /**
11161
             * @psalm-suppress ImpureFunctionCall - is is only a warning
11162
             */
11163
            \trigger_error('UTF8::strtolower() without intl cannot handle the "lang"-parameter: ' . $lang, \E_USER_WARNING);
11164
        }
11165
11166
        // always fallback via symfony polyfill
11167 10
        return \mb_strtoupper($str, $encoding);
11168
    }
11169
11170
    /**
11171
     * Translate characters or replace sub-strings.
11172
     *
11173
     * <p>
11174
     * <br>
11175
     * Examples:
11176
     * <br>
11177
     * <br>
11178
     * <code>
11179
     * UTF8::strtr(string $str, string $from, string $to): string
11180
     * </code>
11181
     * <br><br>
11182
     * <code>
11183
     * UTF8::strtr(string $str, array $replace_pairs): string
11184
     * </code>
11185
     * </p>
11186
     *
11187
     * @see http://php.net/manual/en/function.strtr.php
11188
     *
11189
     * @param string          $str  <p>The string being translated.</p>
11190
     * @param string|string[] $from <p>The string replacing from.</p>
11191
     * @param string|string[] $to   [optional] <p>The string being translated to to.</p>
11192
     *
11193
     * @psalm-pure
11194
     *
11195
     * @return string
11196
     *                <p>This function returns a copy of str, translating all occurrences of each character in "from"
11197
     *                to the corresponding character in "to".</p>
11198
     */
11199 2
    public static function strtr(string $str, $from, $to = ''): string
11200
    {
11201 2
        if ($str === '') {
11202
            return '';
11203
        }
11204
11205 2
        if ($from === $to) {
11206
            return $str;
11207
        }
11208
11209 2
        if ($to !== '') {
11210 2
            if (!\is_array($from)) {
11211 2
                $from = self::str_split($from);
11212
            }
11213
11214 2
            if (!\is_array($to)) {
11215 2
                $to = self::str_split($to);
11216
            }
11217
11218 2
            $count_from = \count($from);
11219 2
            $count_to = \count($to);
11220
11221 2
            if ($count_from !== $count_to) {
11222 2
                if ($count_from > $count_to) {
11223 2
                    $from = \array_slice($from, 0, $count_to);
11224 2
                } elseif ($count_from < $count_to) {
11225 2
                    $to = \array_slice($to, 0, $count_from);
11226
                }
11227
            }
11228
11229 2
            $from = \array_combine($from, $to);
11230
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
11231 2
            if ($from === false) {
11232
                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) . ')');
11233
            }
11234
        }
11235
11236 2
        if (\is_string($from)) {
11237 2
            return \str_replace($from, $to, $str);
0 ignored issues
show
Bug introduced by
It seems like $to can also be of type array<mixed,array>; however, parameter $replace of str_replace() does only seem to accept string|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

11237
            return \str_replace($from, /** @scrutinizer ignore-type */ $to, $str);
Loading history...
11238
        }
11239
11240 2
        return \strtr($str, $from);
11241
    }
11242
11243
    /**
11244
     * Return the width of a string.
11245
     *
11246
     * @param string $str        <p>The input string.</p>
11247
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
11248
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
11249
     *
11250
     * @psalm-pure
11251
     *
11252
     * @return int
11253
     */
11254 2
    public static function strwidth(
11255
        string $str,
11256
        string $encoding = 'UTF-8',
11257
        bool $clean_utf8 = false
11258
    ): int {
11259 2
        if ($str === '') {
11260 2
            return 0;
11261
        }
11262
11263 2
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
11264 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
11265
        }
11266
11267 2
        if ($clean_utf8) {
11268
            // iconv and mbstring are not tolerant to invalid encoding
11269
            // further, their behaviour is inconsistent with that of PHP's substr
11270 2
            $str = self::clean($str);
11271
        }
11272
11273
        //
11274
        // fallback via mbstring
11275
        //
11276
11277 2
        if (self::$SUPPORT['mbstring'] === true) {
11278 2
            if ($encoding === 'UTF-8') {
11279 2
                return \mb_strwidth($str);
11280
            }
11281
11282
            return \mb_strwidth($str, $encoding);
11283
        }
11284
11285
        //
11286
        // fallback via vanilla php
11287
        //
11288
11289
        if ($encoding !== 'UTF-8') {
11290
            $str = self::encode('UTF-8', $str, false, $encoding);
11291
        }
11292
11293
        $wide = 0;
11294
        $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);
11295
11296
        return ($wide << 1) + (int) self::strlen($str, 'UTF-8');
11297
    }
11298
11299
    /**
11300
     * Get part of a string.
11301
     *
11302
     * @see http://php.net/manual/en/function.mb-substr.php
11303
     *
11304
     * @param string $str        <p>The string being checked.</p>
11305
     * @param int    $offset     <p>The first position used in str.</p>
11306
     * @param int    $length     [optional] <p>The maximum length of the returned string.</p>
11307
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
11308
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
11309
     *
11310
     * @psalm-pure
11311
     *
11312
     * @return false|string
11313
     *                      The portion of <i>str</i> specified by the <i>offset</i> and
11314
     *                      <i>length</i> parameters.</p><p>If <i>str</i> is shorter than <i>offset</i>
11315
     *                      characters long, <b>FALSE</b> will be returned.
11316
     */
11317 172
    public static function substr(
11318
        string $str,
11319
        int $offset = 0,
11320
        int $length = null,
11321
        string $encoding = 'UTF-8',
11322
        bool $clean_utf8 = false
11323
    ) {
11324
        // empty string
11325 172
        if ($str === '' || $length === 0) {
11326 8
            return '';
11327
        }
11328
11329 168
        if ($clean_utf8) {
11330
            // iconv and mbstring are not tolerant to invalid encoding
11331
            // further, their behaviour is inconsistent with that of PHP's substr
11332 2
            $str = self::clean($str);
11333
        }
11334
11335
        // whole string
11336 168
        if (!$offset && $length === null) {
11337 7
            return $str;
11338
        }
11339
11340 163
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
11341 19
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
11342
        }
11343
11344
        //
11345
        // fallback via mbstring
11346
        //
11347
11348 163
        if (self::$SUPPORT['mbstring'] === true && $encoding === 'UTF-8') {
11349 161
            if ($length === null) {
11350 64
                return \mb_substr($str, $offset);
11351
            }
11352
11353 102
            return \mb_substr($str, $offset, $length);
11354
        }
11355
11356
        //
11357
        // fallback for binary || ascii only
11358
        //
11359
11360
        if (
11361 4
            $encoding === 'CP850'
11362
            ||
11363 4
            $encoding === 'ASCII'
11364
        ) {
11365
            if ($length === null) {
11366
                return \substr($str, $offset);
11367
            }
11368
11369
            return \substr($str, $offset, $length);
11370
        }
11371
11372
        // otherwise we need the string-length
11373 4
        $str_length = 0;
11374 4
        if ($offset || $length === null) {
11375 4
            $str_length = self::strlen($str, $encoding);
11376
        }
11377
11378
        // e.g.: invalid chars + mbstring not installed
11379 4
        if ($str_length === false) {
11380
            return false;
11381
        }
11382
11383
        // empty string
11384 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...
11385
            return '';
11386
        }
11387
11388
        // impossible
11389 4
        if ($offset && $offset > $str_length) {
11390
            return '';
11391
        }
11392
11393 4
        if ($length === null) {
11394 4
            $length = (int) $str_length;
11395
        } else {
11396 2
            $length = (int) $length;
11397
        }
11398
11399
        if (
11400 4
            $encoding !== 'UTF-8'
11401
            &&
11402 4
            self::$SUPPORT['mbstring'] === false
11403
        ) {
11404
            /**
11405
             * @psalm-suppress ImpureFunctionCall - is is only a warning
11406
             */
11407 2
            \trigger_error('UTF8::substr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
11408
        }
11409
11410
        //
11411
        // fallback via intl
11412
        //
11413
11414
        if (
11415 4
            $encoding === 'UTF-8' // INFO: "grapheme_substr()" can't handle other encodings
11416
            &&
11417 4
            $offset >= 0 // grapheme_substr() can't handle negative offset
11418
            &&
11419 4
            self::$SUPPORT['intl'] === true
11420
        ) {
11421
            $return_tmp = \grapheme_substr($str, $offset, $length);
11422
            if ($return_tmp !== false) {
11423
                return $return_tmp;
11424
            }
11425
        }
11426
11427
        //
11428
        // fallback via iconv
11429
        //
11430
11431
        if (
11432 4
            $length >= 0 // "iconv_substr()" can't handle negative length
11433
            &&
11434 4
            self::$SUPPORT['iconv'] === true
11435
        ) {
11436
            $return_tmp = \iconv_substr($str, $offset, $length);
11437
            if ($return_tmp !== false) {
11438
                return $return_tmp;
11439
            }
11440
        }
11441
11442
        //
11443
        // fallback for ascii only
11444
        //
11445
11446 4
        if (ASCII::is_ascii($str)) {
11447
            return \substr($str, $offset, $length);
11448
        }
11449
11450
        //
11451
        // fallback via vanilla php
11452
        //
11453
11454
        // split to array, and remove invalid characters
11455 4
        $array = self::str_split($str);
11456
11457
        // extract relevant part, and join to make sting again
11458 4
        return \implode('', \array_slice($array, $offset, $length));
11459
    }
11460
11461
    /**
11462
     * Binary-safe comparison of two strings from an offset, up to a length of characters.
11463
     *
11464
     * @param string   $str1               <p>The main string being compared.</p>
11465
     * @param string   $str2               <p>The secondary string being compared.</p>
11466
     * @param int      $offset             [optional] <p>The start position for the comparison. If negative, it starts
11467
     *                                     counting from the end of the string.</p>
11468
     * @param int|null $length             [optional] <p>The length of the comparison. The default value is the largest
11469
     *                                     of the length of the str compared to the length of main_str less the
11470
     *                                     offset.</p>
11471
     * @param bool     $case_insensitivity [optional] <p>If case_insensitivity is TRUE, comparison is case
11472
     *                                     insensitive.</p>
11473
     * @param string   $encoding           [optional] <p>Set the charset for e.g. "mb_" function</p>
11474
     *
11475
     * @psalm-pure
11476
     *
11477
     * @return int
11478
     *             <strong>&lt; 0</strong> if str1 is less than str2;<br>
11479
     *             <strong>&gt; 0</strong> if str1 is greater than str2,<br>
11480
     *             <strong>0</strong> if they are equal
11481
     */
11482 2
    public static function substr_compare(
11483
        string $str1,
11484
        string $str2,
11485
        int $offset = 0,
11486
        int $length = null,
11487
        bool $case_insensitivity = false,
11488
        string $encoding = 'UTF-8'
11489
    ): int {
11490
        if (
11491 2
            $offset !== 0
11492
            ||
11493 2
            $length !== null
11494
        ) {
11495 2
            if ($encoding === 'UTF-8') {
11496 2
                if ($length === null) {
11497 2
                    $str1 = (string) \mb_substr($str1, $offset);
11498
                } else {
11499 2
                    $str1 = (string) \mb_substr($str1, $offset, $length);
11500
                }
11501 2
                $str2 = (string) \mb_substr($str2, 0, (int) self::strlen($str1));
11502
            } else {
11503
                $encoding = self::normalize_encoding($encoding, 'UTF-8');
11504
11505
                $str1 = (string) self::substr($str1, $offset, $length, $encoding);
11506
                $str2 = (string) self::substr($str2, 0, (int) self::strlen($str1), $encoding);
11507
            }
11508
        }
11509
11510 2
        if ($case_insensitivity) {
11511 2
            return self::strcasecmp($str1, $str2, $encoding);
11512
        }
11513
11514 2
        return self::strcmp($str1, $str2);
11515
    }
11516
11517
    /**
11518
     * Count the number of substring occurrences.
11519
     *
11520
     * @see http://php.net/manual/en/function.substr-count.php
11521
     *
11522
     * @param string $haystack   <p>The string to search in.</p>
11523
     * @param string $needle     <p>The substring to search for.</p>
11524
     * @param int    $offset     [optional] <p>The offset where to start counting.</p>
11525
     * @param int    $length     [optional] <p>
11526
     *                           The maximum length after the specified offset to search for the
11527
     *                           substring. It outputs a warning if the offset plus the length is
11528
     *                           greater than the haystack length.
11529
     *                           </p>
11530
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
11531
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
11532
     *
11533
     * @psalm-pure
11534
     *
11535
     * @return false|int
11536
     *                   <p>This functions returns an integer or false if there isn't a string.</p>
11537
     */
11538 5
    public static function substr_count(
11539
        string $haystack,
11540
        string $needle,
11541
        int $offset = 0,
11542
        int $length = null,
11543
        string $encoding = 'UTF-8',
11544
        bool $clean_utf8 = false
11545
    ) {
11546 5
        if ($haystack === '' || $needle === '') {
11547 2
            return false;
11548
        }
11549
11550 5
        if ($length === 0) {
11551 2
            return 0;
11552
        }
11553
11554 5
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
11555 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
11556
        }
11557
11558 5
        if ($clean_utf8) {
11559
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
11560
            // if invalid characters are found in $haystack before $needle
11561
            $needle = self::clean($needle);
11562
            $haystack = self::clean($haystack);
11563
        }
11564
11565 5
        if ($offset || $length > 0) {
11566 2
            if ($length === null) {
11567 2
                $length_tmp = self::strlen($haystack, $encoding);
11568 2
                if ($length_tmp === false) {
11569
                    return false;
11570
                }
11571 2
                $length = (int) $length_tmp;
11572
            }
11573
11574 2
            if ($encoding === 'UTF-8') {
11575 2
                $haystack = (string) \mb_substr($haystack, $offset, $length);
11576
            } else {
11577 2
                $haystack = (string) \mb_substr($haystack, $offset, $length, $encoding);
11578
            }
11579
        }
11580
11581
        if (
11582 5
            $encoding !== 'UTF-8'
11583
            &&
11584 5
            self::$SUPPORT['mbstring'] === false
11585
        ) {
11586
            /**
11587
             * @psalm-suppress ImpureFunctionCall - is is only a warning
11588
             */
11589
            \trigger_error('UTF8::substr_count() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
11590
        }
11591
11592 5
        if (self::$SUPPORT['mbstring'] === true) {
11593 5
            if ($encoding === 'UTF-8') {
11594 5
                return \mb_substr_count($haystack, $needle);
11595
            }
11596
11597 2
            return \mb_substr_count($haystack, $needle, $encoding);
11598
        }
11599
11600
        \preg_match_all('/' . \preg_quote($needle, '/') . '/us', $haystack, $matches, \PREG_SET_ORDER);
11601
11602
        return \count($matches);
11603
    }
11604
11605
    /**
11606
     * Count the number of substring occurrences.
11607
     *
11608
     * @param string $haystack <p>
11609
     *                         The string being checked.
11610
     *                         </p>
11611
     * @param string $needle   <p>
11612
     *                         The string being found.
11613
     *                         </p>
11614
     * @param int    $offset   [optional] <p>
11615
     *                         The offset where to start counting
11616
     *                         </p>
11617
     * @param int    $length   [optional] <p>
11618
     *                         The maximum length after the specified offset to search for the
11619
     *                         substring. It outputs a warning if the offset plus the length is
11620
     *                         greater than the haystack length.
11621
     *                         </p>
11622
     *
11623
     * @psalm-pure
11624
     *
11625
     * @return false|int
11626
     *                   <p>The number of times the
11627
     *                   needle substring occurs in the
11628
     *                   haystack string.</p>
11629
     */
11630 4
    public static function substr_count_in_byte(
11631
        string $haystack,
11632
        string $needle,
11633
        int $offset = 0,
11634
        int $length = null
11635
    ) {
11636 4
        if ($haystack === '' || $needle === '') {
11637 1
            return 0;
11638
        }
11639
11640
        if (
11641 3
            ($offset || $length !== null)
11642
            &&
11643 3
            self::$SUPPORT['mbstring_func_overload'] === true
11644
        ) {
11645
            if ($length === null) {
11646
                $length_tmp = self::strlen($haystack);
11647
                if ($length_tmp === false) {
11648
                    return false;
11649
                }
11650
                $length = (int) $length_tmp;
11651
            }
11652
11653
            if (
11654
                (
11655
                    $length !== 0
11656
                    &&
11657
                    $offset !== 0
11658
                )
11659
                &&
11660
                ($length + $offset) <= 0
11661
                &&
11662
                !Bootup::is_php('7.1') // output from "substr_count()" have changed in PHP 7.1
11663
            ) {
11664
                return false;
11665
            }
11666
11667
            /** @var false|string $haystack_tmp - needed for PhpStan (stubs error) */
11668
            $haystack_tmp = \substr($haystack, $offset, $length);
11669
            if ($haystack_tmp === false) {
11670
                $haystack_tmp = '';
11671
            }
11672
            $haystack = (string) $haystack_tmp;
11673
        }
11674
11675 3
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
11676
            // "mb_" is available if overload is used, so use it ...
11677
            return \mb_substr_count($haystack, $needle, 'CP850'); // 8-BIT
11678
        }
11679
11680 3
        if ($length === null) {
11681 3
            return \substr_count($haystack, $needle, $offset);
11682
        }
11683
11684
        return \substr_count($haystack, $needle, $offset, $length);
11685
    }
11686
11687
    /**
11688
     * Returns the number of occurrences of $substring in the given string.
11689
     * By default, the comparison is case-sensitive, but can be made insensitive
11690
     * by setting $case_sensitive to false.
11691
     *
11692
     * @param string $str            <p>The input string.</p>
11693
     * @param string $substring      <p>The substring to search for.</p>
11694
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
11695
     * @param string $encoding       [optional] <p>Set the charset for e.g. "mb_" function</p>
11696
     *
11697
     * @psalm-pure
11698
     *
11699
     * @return int
11700
     */
11701 15
    public static function substr_count_simple(
11702
        string $str,
11703
        string $substring,
11704
        bool $case_sensitive = true,
11705
        string $encoding = 'UTF-8'
11706
    ): int {
11707 15
        if ($str === '' || $substring === '') {
11708 2
            return 0;
11709
        }
11710
11711 13
        if ($encoding === 'UTF-8') {
11712 7
            if ($case_sensitive) {
11713
                return (int) \mb_substr_count($str, $substring);
11714
            }
11715
11716 7
            return (int) \mb_substr_count(
11717 7
                \mb_strtoupper($str),
11718 7
                \mb_strtoupper($substring)
11719
            );
11720
        }
11721
11722 6
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
11723
11724 6
        if ($case_sensitive) {
11725 3
            return (int) \mb_substr_count($str, $substring, $encoding);
11726
        }
11727
11728 3
        return (int) \mb_substr_count(
11729 3
            self::strtocasefold($str, true, false, $encoding, null, false),
11730 3
            self::strtocasefold($substring, true, false, $encoding, null, false),
11731 3
            $encoding
11732
        );
11733
    }
11734
11735
    /**
11736
     * Removes a prefix ($needle) from the beginning of the string ($haystack), case-insensitive.
11737
     *
11738
     * @param string $haystack <p>The string to search in.</p>
11739
     * @param string $needle   <p>The substring to search for.</p>
11740
     *
11741
     * @psalm-pure
11742
     *
11743
     * @return string
11744
     *                <p>Return the sub-string.</p>
11745
     */
11746 2
    public static function substr_ileft(string $haystack, string $needle): string
11747
    {
11748 2
        if ($haystack === '') {
11749 2
            return '';
11750
        }
11751
11752 2
        if ($needle === '') {
11753 2
            return $haystack;
11754
        }
11755
11756 2
        if (self::str_istarts_with($haystack, $needle)) {
11757 2
            $haystack = (string) \mb_substr($haystack, (int) self::strlen($needle));
11758
        }
11759
11760 2
        return $haystack;
11761
    }
11762
11763
    /**
11764
     * Get part of a string process in bytes.
11765
     *
11766
     * @param string $str    <p>The string being checked.</p>
11767
     * @param int    $offset <p>The first position used in str.</p>
11768
     * @param int    $length [optional] <p>The maximum length of the returned string.</p>
11769
     *
11770
     * @psalm-pure
11771
     *
11772
     * @return false|string
11773
     *                      The portion of <i>str</i> specified by the <i>offset</i> and
11774
     *                      <i>length</i> parameters.</p><p>If <i>str</i> is shorter than <i>offset</i>
11775
     *                      characters long, <b>FALSE</b> will be returned.
11776
     */
11777 1
    public static function substr_in_byte(string $str, int $offset = 0, int $length = null)
11778
    {
11779
        // empty string
11780 1
        if ($str === '' || $length === 0) {
11781
            return '';
11782
        }
11783
11784
        // whole string
11785 1
        if (!$offset && $length === null) {
11786
            return $str;
11787
        }
11788
11789 1
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
11790
            // "mb_" is available if overload is used, so use it ...
11791
            return \mb_substr($str, $offset, $length, 'CP850'); // 8-BIT
11792
        }
11793
11794 1
        return \substr($str, $offset, $length ?? 2147483647);
11795
    }
11796
11797
    /**
11798
     * Removes a suffix ($needle) from the end of the string ($haystack), case-insensitive.
11799
     *
11800
     * @param string $haystack <p>The string to search in.</p>
11801
     * @param string $needle   <p>The substring to search for.</p>
11802
     *
11803
     * @psalm-pure
11804
     *
11805
     * @return string
11806
     *                <p>Return the sub-string.<p>
11807
     */
11808 2
    public static function substr_iright(string $haystack, string $needle): string
11809
    {
11810 2
        if ($haystack === '') {
11811 2
            return '';
11812
        }
11813
11814 2
        if ($needle === '') {
11815 2
            return $haystack;
11816
        }
11817
11818 2
        if (self::str_iends_with($haystack, $needle)) {
11819 2
            $haystack = (string) \mb_substr($haystack, 0, (int) self::strlen($haystack) - (int) self::strlen($needle));
11820
        }
11821
11822 2
        return $haystack;
11823
    }
11824
11825
    /**
11826
     * Removes a prefix ($needle) from the beginning of the string ($haystack).
11827
     *
11828
     * @param string $haystack <p>The string to search in.</p>
11829
     * @param string $needle   <p>The substring to search for.</p>
11830
     *
11831
     * @psalm-pure
11832
     *
11833
     * @return string
11834
     *                <p>Return the sub-string.</p>
11835
     */
11836 2
    public static function substr_left(string $haystack, string $needle): string
11837
    {
11838 2
        if ($haystack === '') {
11839 2
            return '';
11840
        }
11841
11842 2
        if ($needle === '') {
11843 2
            return $haystack;
11844
        }
11845
11846 2
        if (self::str_starts_with($haystack, $needle)) {
11847 2
            $haystack = (string) \mb_substr($haystack, (int) self::strlen($needle));
11848
        }
11849
11850 2
        return $haystack;
11851
    }
11852
11853
    /**
11854
     * Replace text within a portion of a string.
11855
     *
11856
     * source: https://gist.github.com/stemar/8287074
11857
     *
11858
     * @param string|string[] $str         <p>The input string or an array of stings.</p>
11859
     * @param string|string[] $replacement <p>The replacement string or an array of stings.</p>
11860
     * @param int|int[]       $offset      <p>
11861
     *                                     If start is positive, the replacing will begin at the start'th offset
11862
     *                                     into string.
11863
     *                                     <br><br>
11864
     *                                     If start is negative, the replacing will begin at the start'th character
11865
     *                                     from the end of string.
11866
     *                                     </p>
11867
     * @param int|int[]|null  $length      [optional] <p>If given and is positive, it represents the length of the
11868
     *                                     portion of string which is to be replaced. If it is negative, it
11869
     *                                     represents the number of characters from the end of string at which to
11870
     *                                     stop replacing. If it is not given, then it will default to strlen(
11871
     *                                     string ); i.e. end the replacing at the end of string. Of course, if
11872
     *                                     length is zero then this function will have the effect of inserting
11873
     *                                     replacement into string at the given start offset.</p>
11874
     * @param string          $encoding    [optional] <p>Set the charset for e.g. "mb_" function</p>
11875
     *
11876
     * @psalm-pure
11877
     *
11878
     * @return string|string[]
11879
     *                         <p>The result string is returned. If string is an array then array is returned.</p>
11880
     */
11881 10
    public static function substr_replace(
11882
        $str,
11883
        $replacement,
11884
        $offset,
11885
        $length = null,
11886
        string $encoding = 'UTF-8'
11887
    ) {
11888 10
        if (\is_array($str)) {
11889 1
            $num = \count($str);
11890
11891
            // the replacement
11892 1
            if (\is_array($replacement)) {
11893 1
                $replacement = \array_slice($replacement, 0, $num);
11894
            } else {
11895 1
                $replacement = \array_pad([$replacement], $num, $replacement);
11896
            }
11897
11898
            // the offset
11899 1
            if (\is_array($offset)) {
11900 1
                $offset = \array_slice($offset, 0, $num);
11901 1
                foreach ($offset as &$value_tmp) {
11902 1
                    $value_tmp = (int) $value_tmp === $value_tmp ? $value_tmp : 0;
11903
                }
11904 1
                unset($value_tmp);
11905
            } else {
11906 1
                $offset = \array_pad([$offset], $num, $offset);
11907
            }
11908
11909
            // the length
11910 1
            if ($length === null) {
11911 1
                $length = \array_fill(0, $num, 0);
11912 1
            } elseif (\is_array($length)) {
11913 1
                $length = \array_slice($length, 0, $num);
11914 1
                foreach ($length as &$value_tmp_V2) {
11915 1
                    $value_tmp_V2 = (int) $value_tmp_V2 === $value_tmp_V2 ? $value_tmp_V2 : $num;
11916
                }
11917 1
                unset($value_tmp_V2);
11918
            } else {
11919 1
                $length = \array_pad([$length], $num, $length);
11920
            }
11921
11922
            // recursive call
11923 1
            return \array_map([self::class, 'substr_replace'], $str, $replacement, $offset, $length);
11924
        }
11925
11926 10
        if (\is_array($replacement)) {
11927 1
            if ($replacement !== []) {
11928 1
                $replacement = $replacement[0];
11929
            } else {
11930 1
                $replacement = '';
11931
            }
11932
        }
11933
11934
        // init
11935 10
        $str = (string) $str;
11936 10
        $replacement = (string) $replacement;
11937
11938 10
        if (\is_array($length)) {
11939
            throw new \InvalidArgumentException('Parameter "$length" can only be an array, if "$str" is also an array.');
11940
        }
11941
11942 10
        if (\is_array($offset)) {
11943
            throw new \InvalidArgumentException('Parameter "$offset" can only be an array, if "$str" is also an array.');
11944
        }
11945
11946 10
        if ($str === '') {
11947 1
            return $replacement;
11948
        }
11949
11950 9
        if (self::$SUPPORT['mbstring'] === true) {
11951 9
            $string_length = (int) self::strlen($str, $encoding);
11952
11953 9
            if ($offset < 0) {
11954 1
                $offset = (int) \max(0, $string_length + $offset);
11955 9
            } elseif ($offset > $string_length) {
11956 1
                $offset = $string_length;
11957
            }
11958
11959 9
            if ($length !== null && $length < 0) {
11960 1
                $length = (int) \max(0, $string_length - $offset + $length);
11961 9
            } elseif ($length === null || $length > $string_length) {
11962 4
                $length = $string_length;
11963
            }
11964
11965
            /** @noinspection AdditionOperationOnArraysInspection */
11966 9
            if (($offset + $length) > $string_length) {
11967 4
                $length = $string_length - $offset;
11968
            }
11969
11970
            /** @noinspection AdditionOperationOnArraysInspection */
11971 9
            return ((string) \mb_substr($str, 0, $offset, $encoding)) .
11972 9
                   $replacement .
11973 9
                   ((string) \mb_substr($str, $offset + $length, $string_length - $offset - $length, $encoding));
11974
        }
11975
11976
        //
11977
        // fallback for ascii only
11978
        //
11979
11980
        if (ASCII::is_ascii($str)) {
11981
            return ($length === null) ?
11982
                \substr_replace($str, $replacement, $offset) :
11983
                \substr_replace($str, $replacement, $offset, $length);
11984
        }
11985
11986
        //
11987
        // fallback via vanilla php
11988
        //
11989
11990
        \preg_match_all('/./us', $str, $str_matches);
11991
        \preg_match_all('/./us', $replacement, $replacement_matches);
11992
11993
        if ($length === null) {
11994
            $length_tmp = self::strlen($str, $encoding);
11995
            if ($length_tmp === false) {
11996
                // e.g.: non mbstring support + invalid chars
11997
                return '';
11998
            }
11999
            $length = (int) $length_tmp;
12000
        }
12001
12002
        \array_splice($str_matches[0], $offset, $length, $replacement_matches[0]);
12003
12004
        return \implode('', $str_matches[0]);
12005
    }
12006
12007
    /**
12008
     * Removes a suffix ($needle) from the end of the string ($haystack).
12009
     *
12010
     * @param string $haystack <p>The string to search in.</p>
12011
     * @param string $needle   <p>The substring to search for.</p>
12012
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
12013
     *
12014
     * @psalm-pure
12015
     *
12016
     * @return string
12017
     *                <p>Return the sub-string.</p>
12018
     */
12019 2
    public static function substr_right(
12020
        string $haystack,
12021
        string $needle,
12022
        string $encoding = 'UTF-8'
12023
    ): string {
12024 2
        if ($haystack === '') {
12025 2
            return '';
12026
        }
12027
12028 2
        if ($needle === '') {
12029 2
            return $haystack;
12030
        }
12031
12032
        if (
12033 2
            $encoding === 'UTF-8'
12034
            &&
12035 2
            \substr($haystack, -\strlen($needle)) === $needle
12036
        ) {
12037 2
            return (string) \mb_substr($haystack, 0, (int) \mb_strlen($haystack) - (int) \mb_strlen($needle));
12038
        }
12039
12040 2
        if (\substr($haystack, -\strlen($needle)) === $needle) {
12041
            return (string) self::substr(
12042
                $haystack,
12043
                0,
12044
                (int) self::strlen($haystack, $encoding) - (int) self::strlen($needle, $encoding),
12045
                $encoding
12046
            );
12047
        }
12048
12049 2
        return $haystack;
12050
    }
12051
12052
    /**
12053
     * Returns a case swapped version of the string.
12054
     *
12055
     * @param string $str        <p>The input string.</p>
12056
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
12057
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
12058
     *
12059
     * @psalm-pure
12060
     *
12061
     * @return string
12062
     *                <p>Each character's case swapped.</p>
12063
     */
12064 6
    public static function swapCase(string $str, string $encoding = 'UTF-8', bool $clean_utf8 = false): string
12065
    {
12066 6
        if ($str === '') {
12067 1
            return '';
12068
        }
12069
12070 6
        if ($clean_utf8) {
12071
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
12072
            // if invalid characters are found in $haystack before $needle
12073 2
            $str = self::clean($str);
12074
        }
12075
12076 6
        if ($encoding === 'UTF-8') {
12077 4
            return (string) (\mb_strtolower($str) ^ \mb_strtoupper($str) ^ $str);
12078
        }
12079
12080 4
        return (string) (self::strtolower($str, $encoding) ^ self::strtoupper($str, $encoding) ^ $str);
12081
    }
12082
12083
    /**
12084
     * Checks whether symfony-polyfills are used.
12085
     *
12086
     * @psalm-pure
12087
     *
12088
     * @return bool
12089
     *              <strong>true</strong> if in use, <strong>false</strong> otherwise
12090
     */
12091
    public static function symfony_polyfill_used(): bool
12092
    {
12093
        // init
12094
        $return = false;
12095
12096
        $return_tmp = \extension_loaded('mbstring');
12097
        if (!$return_tmp && \function_exists('mb_strlen')) {
12098
            $return = true;
12099
        }
12100
12101
        $return_tmp = \extension_loaded('iconv');
12102
        if (!$return_tmp && \function_exists('iconv')) {
12103
            $return = true;
12104
        }
12105
12106
        return $return;
12107
    }
12108
12109
    /**
12110
     * @param string $str
12111
     * @param int    $tab_length
12112
     *
12113
     * @psalm-pure
12114
     *
12115
     * @return string
12116
     */
12117 6
    public static function tabs_to_spaces(string $str, int $tab_length = 4): string
12118
    {
12119 6
        if ($tab_length === 4) {
12120 3
            $spaces = '    ';
12121 3
        } elseif ($tab_length === 2) {
12122 1
            $spaces = '  ';
12123
        } else {
12124 2
            $spaces = \str_repeat(' ', $tab_length);
12125
        }
12126
12127 6
        return \str_replace("\t", $spaces, $str);
12128
    }
12129
12130
    /**
12131
     * Converts the first character of each word in the string to uppercase
12132
     * and all other chars to lowercase.
12133
     *
12134
     * @param string      $str                           <p>The input string.</p>
12135
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
12136
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
12137
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt,
12138
     *                                                   tr</p>
12139
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ
12140
     *                                                   -> ß</p>
12141
     *
12142
     * @psalm-pure
12143
     *
12144
     * @return string
12145
     *                <p>A string with all characters of $str being title-cased.</p>
12146
     */
12147 5
    public static function titlecase(
12148
        string $str,
12149
        string $encoding = 'UTF-8',
12150
        bool $clean_utf8 = false,
12151
        string $lang = null,
12152
        bool $try_to_keep_the_string_length = false
12153
    ): string {
12154 5
        if ($clean_utf8) {
12155
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
12156
            // if invalid characters are found in $haystack before $needle
12157
            $str = self::clean($str);
12158
        }
12159
12160
        if (
12161 5
            $lang === null
12162
            &&
12163 5
            !$try_to_keep_the_string_length
12164
        ) {
12165 5
            if ($encoding === 'UTF-8') {
12166 3
                return \mb_convert_case($str, \MB_CASE_TITLE);
12167
            }
12168
12169 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
12170
12171 2
            return \mb_convert_case($str, \MB_CASE_TITLE, $encoding);
12172
        }
12173
12174
        return self::str_titleize(
12175
            $str,
12176
            null,
12177
            $encoding,
12178
            false,
12179
            $lang,
12180
            $try_to_keep_the_string_length,
12181
            false
12182
        );
12183
    }
12184
12185
    /**
12186
     * alias for "UTF8::to_ascii()"
12187
     *
12188
     * @param string $str
12189
     * @param string $subst_chr
12190
     * @param bool   $strict
12191
     *
12192
     * @psalm-pure
12193
     *
12194
     * @return string
12195
     *
12196
     * @see        UTF8::to_ascii()
12197
     * @deprecated <p>please use "UTF8::to_ascii()"</p>
12198
     */
12199 7
    public static function toAscii(
12200
        string $str,
12201
        string $subst_chr = '?',
12202
        bool $strict = false
12203
    ): string {
12204 7
        return self::to_ascii($str, $subst_chr, $strict);
12205
    }
12206
12207
    /**
12208
     * alias for "UTF8::to_iso8859()"
12209
     *
12210
     * @param string|string[] $str
12211
     *
12212
     * @psalm-pure
12213
     *
12214
     * @return string|string[]
12215
     *
12216
     * @see        UTF8::to_iso8859()
12217
     * @deprecated <p>please use "UTF8::to_iso8859()"</p>
12218
     */
12219 2
    public static function toIso8859($str)
12220
    {
12221 2
        return self::to_iso8859($str);
12222
    }
12223
12224
    /**
12225
     * alias for "UTF8::to_latin1()"
12226
     *
12227
     * @param string|string[] $str
12228
     *
12229
     * @psalm-pure
12230
     *
12231
     * @return string|string[]
12232
     *
12233
     * @see        UTF8::to_iso8859()
12234
     * @deprecated <p>please use "UTF8::to_iso8859()"</p>
12235
     */
12236 2
    public static function toLatin1($str)
12237
    {
12238 2
        return self::to_iso8859($str);
12239
    }
12240
12241
    /**
12242
     * alias for "UTF8::to_utf8()"
12243
     *
12244
     * @param string|string[] $str
12245
     *
12246
     * @psalm-pure
12247
     *
12248
     * @return string|string[]
12249
     *
12250
     * @see        UTF8::to_utf8()
12251
     * @deprecated <p>please use "UTF8::to_utf8()"</p>
12252
     */
12253 2
    public static function toUTF8($str)
12254
    {
12255 2
        return self::to_utf8($str);
12256
    }
12257
12258
    /**
12259
     * Convert a string into ASCII.
12260
     *
12261
     * @param string $str     <p>The input string.</p>
12262
     * @param string $unknown [optional] <p>Character use if character unknown. (default is ?)</p>
12263
     * @param bool   $strict  [optional] <p>Use "transliterator_transliterate()" from PHP-Intl | WARNING: bad
12264
     *                        performance</p>
12265
     *
12266
     * @psalm-pure
12267
     *
12268
     * @return string
12269
     */
12270 37
    public static function to_ascii(
12271
        string $str,
12272
        string $unknown = '?',
12273
        bool $strict = false
12274
    ): string {
12275 37
        return ASCII::to_transliterate($str, $unknown, $strict);
12276
    }
12277
12278
    /**
12279
     * @param mixed $str
12280
     *
12281
     * @psalm-pure
12282
     *
12283
     * @return bool
12284
     */
12285 19
    public static function to_boolean($str): bool
12286
    {
12287
        // init
12288 19
        $str = (string) $str;
12289
12290 19
        if ($str === '') {
12291 2
            return false;
12292
        }
12293
12294
        // Info: http://php.net/manual/en/filter.filters.validate.php
12295
        $map = [
12296 17
            'true'  => true,
12297
            '1'     => true,
12298
            'on'    => true,
12299
            'yes'   => true,
12300
            'false' => false,
12301
            '0'     => false,
12302
            'off'   => false,
12303
            'no'    => false,
12304
        ];
12305
12306 17
        if (isset($map[$str])) {
12307 11
            return $map[$str];
12308
        }
12309
12310 6
        $key = \strtolower($str);
12311 6
        if (isset($map[$key])) {
12312 2
            return $map[$key];
12313
        }
12314
12315 4
        if (\is_numeric($str)) {
12316 2
            return ((float) $str + 0) > 0;
12317
        }
12318
12319 2
        return (bool) \trim($str);
12320
    }
12321
12322
    /**
12323
     * Convert given string to safe filename (and keep string case).
12324
     *
12325
     * @param string $str
12326
     * @param bool   $use_transliterate No transliteration, conversion etc. is done by default - unsafe characters are
12327
     *                                  simply replaced with hyphen.
12328
     * @param string $fallback_char
12329
     *
12330
     * @psalm-pure
12331
     *
12332
     * @return string
12333
     */
12334 1
    public static function to_filename(
12335
        string $str,
12336
        bool $use_transliterate = false,
12337
        string $fallback_char = '-'
12338
    ): string {
12339 1
        return ASCII::to_filename(
12340 1
            $str,
12341 1
            $use_transliterate,
12342 1
            $fallback_char
12343
        );
12344
    }
12345
12346
    /**
12347
     * Convert a string into "ISO-8859"-encoding (Latin-1).
12348
     *
12349
     * @param string|string[] $str
12350
     *
12351
     * @psalm-pure
12352
     *
12353
     * @return string|string[]
12354
     */
12355 8
    public static function to_iso8859($str)
12356
    {
12357 8
        if (\is_array($str)) {
12358 2
            foreach ($str as $k => &$v) {
12359 2
                $v = self::to_iso8859($v);
12360
            }
12361
12362 2
            return $str;
12363
        }
12364
12365 8
        $str = (string) $str;
12366 8
        if ($str === '') {
12367 2
            return '';
12368
        }
12369
12370 8
        return self::utf8_decode($str);
12371
    }
12372
12373
    /**
12374
     * alias for "UTF8::to_iso8859()"
12375
     *
12376
     * @param string|string[] $str
12377
     *
12378
     * @psalm-pure
12379
     *
12380
     * @return string|string[]
12381
     *
12382
     * @see        UTF8::to_iso8859()
12383
     * @deprecated <p>please use "UTF8::to_iso8859()"</p>
12384
     */
12385 2
    public static function to_latin1($str)
12386
    {
12387 2
        return self::to_iso8859($str);
12388
    }
12389
12390
    /**
12391
     * This function leaves UTF-8 characters alone, while converting almost all non-UTF8 to UTF8.
12392
     *
12393
     * <ul>
12394
     * <li>It decode UTF-8 codepoints and Unicode escape sequences.</li>
12395
     * <li>It assumes that the encoding of the original string is either WINDOWS-1252 or ISO-8859.</li>
12396
     * <li>WARNING: It does not remove invalid UTF-8 characters, so you maybe need to use "UTF8::clean()" for this
12397
     * case.</li>
12398
     * </ul>
12399
     *
12400
     * @param string|string[] $str                        <p>Any string or array.</p>
12401
     * @param bool            $decode_html_entity_to_utf8 <p>Set to true, if you need to decode html-entities.</p>
12402
     *
12403
     * @psalm-pure
12404
     *
12405
     * @return string|string[]
12406
     *                         <p>The UTF-8 encoded string</p>
12407
     *
12408
     * @noinspection SuspiciousBinaryOperationInspection
12409
     */
12410 43
    public static function to_utf8($str, bool $decode_html_entity_to_utf8 = false)
12411
    {
12412 43
        if (\is_array($str)) {
12413 4
            foreach ($str as $k => &$v) {
12414 4
                $v = self::to_utf8($v, $decode_html_entity_to_utf8);
12415
            }
12416
12417 4
            return $str;
12418
        }
12419
12420 43
        $str = (string) $str;
12421 43
        if ($str === '') {
12422 7
            return $str;
12423
        }
12424
12425 43
        $max = \strlen($str);
12426 43
        $buf = '';
12427
12428 43
        for ($i = 0; $i < $max; ++$i) {
12429 43
            $c1 = $str[$i];
12430
12431 43
            if ($c1 >= "\xC0") { // should be converted to UTF8, if it's not UTF8 already
12432
12433 39
                if ($c1 <= "\xDF") { // looks like 2 bytes UTF8
12434
12435 36
                    $c2 = $i + 1 >= $max ? "\x00" : $str[$i + 1];
12436
12437 36
                    if ($c2 >= "\x80" && $c2 <= "\xBF") { // yeah, almost sure it's UTF8 already
12438 22
                        $buf .= $c1 . $c2;
12439 22
                        ++$i;
12440
                    } else { // not valid UTF8 - convert it
12441 36
                        $buf .= self::to_utf8_convert_helper($c1);
12442
                    }
12443 36
                } elseif ($c1 >= "\xE0" && $c1 <= "\xEF") { // looks like 3 bytes UTF8
12444
12445 35
                    $c2 = $i + 1 >= $max ? "\x00" : $str[$i + 1];
12446 35
                    $c3 = $i + 2 >= $max ? "\x00" : $str[$i + 2];
12447
12448 35
                    if ($c2 >= "\x80" && $c2 <= "\xBF" && $c3 >= "\x80" && $c3 <= "\xBF") { // yeah, almost sure it's UTF8 already
12449 17
                        $buf .= $c1 . $c2 . $c3;
12450 17
                        $i += 2;
12451
                    } else { // not valid UTF8 - convert it
12452 35
                        $buf .= self::to_utf8_convert_helper($c1);
12453
                    }
12454 28
                } elseif ($c1 >= "\xF0" && $c1 <= "\xF7") { // looks like 4 bytes UTF8
12455
12456 28
                    $c2 = $i + 1 >= $max ? "\x00" : $str[$i + 1];
12457 28
                    $c3 = $i + 2 >= $max ? "\x00" : $str[$i + 2];
12458 28
                    $c4 = $i + 3 >= $max ? "\x00" : $str[$i + 3];
12459
12460 28
                    if ($c2 >= "\x80" && $c2 <= "\xBF" && $c3 >= "\x80" && $c3 <= "\xBF" && $c4 >= "\x80" && $c4 <= "\xBF") { // yeah, almost sure it's UTF8 already
12461 10
                        $buf .= $c1 . $c2 . $c3 . $c4;
12462 10
                        $i += 3;
12463
                    } else { // not valid UTF8 - convert it
12464 28
                        $buf .= self::to_utf8_convert_helper($c1);
12465
                    }
12466
                } else { // doesn't look like UTF8, but should be converted
12467
12468 39
                    $buf .= self::to_utf8_convert_helper($c1);
12469
                }
12470 40
            } elseif (($c1 & "\xC0") === "\x80") { // needs conversion
12471
12472 4
                $buf .= self::to_utf8_convert_helper($c1);
12473
            } else { // it doesn't need conversion
12474
12475 40
                $buf .= $c1;
12476
            }
12477
        }
12478
12479
        // decode unicode escape sequences + unicode surrogate pairs
12480 43
        $buf = \preg_replace_callback(
12481 43
            '/\\\\u([dD][89abAB][0-9a-fA-F]{2})\\\\u([dD][cdefCDEF][\da-fA-F]{2})|\\\\u([0-9a-fA-F]{4})/',
12482
            /**
12483
             * @param array $matches
12484
             *
12485
             * @psalm-pure
12486
             *
12487
             * @return string
12488
             */
12489
            static function (array $matches): string {
12490 13
                if (isset($matches[3])) {
12491 13
                    $cp = (int) \hexdec($matches[3]);
12492
                } else {
12493
                    // http://unicode.org/faq/utf_bom.html#utf16-4
12494 1
                    $cp = ((int) \hexdec($matches[1]) << 10)
12495 1
                          + (int) \hexdec($matches[2])
12496 1
                          + 0x10000
12497 1
                          - (0xD800 << 10)
12498 1
                          - 0xDC00;
12499
                }
12500
12501
                // https://github.com/php/php-src/blob/php-7.3.2/ext/standard/html.c#L471
12502
                //
12503
                // php_utf32_utf8(unsigned char *buf, unsigned k)
12504
12505 13
                if ($cp < 0x80) {
12506 8
                    return (string) self::chr($cp);
12507
                }
12508
12509 10
                if ($cp < 0xA0) {
12510
                    /** @noinspection UnnecessaryCastingInspection */
12511
                    return (string) self::chr(0xC0 | $cp >> 6) . (string) self::chr(0x80 | $cp & 0x3F);
12512
                }
12513
12514 10
                return self::decimal_to_chr($cp);
12515 43
            },
12516 43
            $buf
12517
        );
12518
12519 43
        if ($buf === null) {
12520
            return '';
12521
        }
12522
12523
        // decode UTF-8 codepoints
12524 43
        if ($decode_html_entity_to_utf8) {
12525 3
            $buf = self::html_entity_decode($buf);
12526
        }
12527
12528 43
        return $buf;
12529
    }
12530
12531
    /**
12532
     * Returns the given string as an integer, or null if the string isn't numeric.
12533
     *
12534
     * @param string $str
12535
     *
12536
     * @psalm-pure
12537
     *
12538
     * @return int|null
12539
     *                  <p>null if the string isn't numeric</p>
12540
     */
12541 1
    public static function to_int(string $str)
12542
    {
12543 1
        if (\is_numeric($str)) {
12544 1
            return (int) $str;
12545
        }
12546
12547 1
        return null;
12548
    }
12549
12550
    /**
12551
     * Returns the given input as string, or null if the input isn't int|float|string
12552
     * and do not implement the "__toString()" method.
12553
     *
12554
     * @param mixed $input
12555
     *
12556
     * @psalm-pure
12557
     *
12558
     * @return string|null
12559
     *                     <p>null if the input isn't int|float|string and has no "__toString()" method</p>
12560
     */
12561 1
    public static function to_string($input)
12562
    {
12563
        /** @var string $input_type - hack for psalm */
12564 1
        $input_type = \gettype($input);
12565
12566
        if (
12567 1
            $input_type === 'string'
12568
            ||
12569 1
            $input_type === 'integer'
12570
            ||
12571 1
            $input_type === 'float'
12572
            ||
12573 1
            $input_type === 'double'
12574
        ) {
12575 1
            return (string) $input;
12576
        }
12577
12578
        if (
12579 1
            $input_type === 'object'
12580
            &&
12581 1
            \method_exists($input, '__toString')
12582
        ) {
12583 1
            return (string) $input;
12584
        }
12585
12586 1
        return null;
12587
    }
12588
12589
    /**
12590
     * Strip whitespace or other characters from the beginning and end of a UTF-8 string.
12591
     *
12592
     * INFO: This is slower then "trim()"
12593
     *
12594
     * We can only use the original-function, if we use <= 7-Bit in the string / chars
12595
     * but the check for ASCII (7-Bit) cost more time, then we can safe here.
12596
     *
12597
     * @param string      $str   <p>The string to be trimmed</p>
12598
     * @param string|null $chars [optional] <p>Optional characters to be stripped</p>
12599
     *
12600
     * @psalm-pure
12601
     *
12602
     * @return string
12603
     *                <p>The trimmed string.</p>
12604
     */
12605 56
    public static function trim(string $str = '', string $chars = null): string
12606
    {
12607 56
        if ($str === '') {
12608 9
            return '';
12609
        }
12610
12611 49
        if (self::$SUPPORT['mbstring'] === true) {
12612 49
            if ($chars) {
12613
                /** @noinspection PregQuoteUsageInspection */
12614 27
                $chars = \preg_quote($chars);
12615 27
                $pattern = "^[${chars}]+|[${chars}]+\$";
12616
            } else {
12617 22
                $pattern = '^[\\s]+|[\\s]+$';
12618
            }
12619
12620
            /** @noinspection PhpComposerExtensionStubsInspection */
12621 49
            return (string) \mb_ereg_replace($pattern, '', $str);
12622
        }
12623
12624 8
        if ($chars) {
12625
            $chars = \preg_quote($chars, '/');
12626
            $pattern = "^[${chars}]+|[${chars}]+\$";
12627
        } else {
12628 8
            $pattern = '^[\\s]+|[\\s]+$';
12629
        }
12630
12631 8
        return self::regex_replace($str, $pattern, '', '', '/');
12632
    }
12633
12634
    /**
12635
     * Makes string's first char uppercase.
12636
     *
12637
     * @param string      $str                           <p>The input string.</p>
12638
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
12639
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
12640
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt,
12641
     *                                                   tr</p>
12642
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ
12643
     *                                                   -> ß</p>
12644
     *
12645
     * @psalm-pure
12646
     *
12647
     * @return string
12648
     *                <p>The resulting string with with char uppercase.</p>
12649
     */
12650 69
    public static function ucfirst(
12651
        string $str,
12652
        string $encoding = 'UTF-8',
12653
        bool $clean_utf8 = false,
12654
        string $lang = null,
12655
        bool $try_to_keep_the_string_length = false
12656
    ): string {
12657 69
        if ($str === '') {
12658 3
            return '';
12659
        }
12660
12661 68
        if ($clean_utf8) {
12662
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
12663
            // if invalid characters are found in $haystack before $needle
12664 1
            $str = self::clean($str);
12665
        }
12666
12667 68
        $use_mb_functions = $lang === null && !$try_to_keep_the_string_length;
12668
12669 68
        if ($encoding === 'UTF-8') {
12670 22
            $str_part_two = (string) \mb_substr($str, 1);
12671
12672 22
            if ($use_mb_functions) {
12673 22
                $str_part_one = \mb_strtoupper(
12674 22
                    (string) \mb_substr($str, 0, 1)
12675
                );
12676
            } else {
12677
                $str_part_one = self::strtoupper(
12678
                    (string) \mb_substr($str, 0, 1),
12679
                    $encoding,
12680
                    false,
12681
                    $lang,
12682 22
                    $try_to_keep_the_string_length
12683
                );
12684
            }
12685
        } else {
12686 47
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
12687
12688 47
            $str_part_two = (string) self::substr($str, 1, null, $encoding);
12689
12690 47
            if ($use_mb_functions) {
12691 47
                $str_part_one = \mb_strtoupper(
12692 47
                    (string) \mb_substr($str, 0, 1, $encoding),
12693 47
                    $encoding
12694
                );
12695
            } else {
12696
                $str_part_one = self::strtoupper(
12697
                    (string) self::substr($str, 0, 1, $encoding),
12698
                    $encoding,
12699
                    false,
12700
                    $lang,
12701
                    $try_to_keep_the_string_length
12702
                );
12703
            }
12704
        }
12705
12706 68
        return $str_part_one . $str_part_two;
12707
    }
12708
12709
    /**
12710
     * alias for "UTF8::ucfirst()"
12711
     *
12712
     * @param string $str
12713
     * @param string $encoding
12714
     * @param bool   $clean_utf8
12715
     *
12716
     * @psalm-pure
12717
     *
12718
     * @return string
12719
     *
12720
     * @see        UTF8::ucfirst()
12721
     * @deprecated <p>please use "UTF8::ucfirst()"</p>
12722
     */
12723 1
    public static function ucword(
12724
        string $str,
12725
        string $encoding = 'UTF-8',
12726
        bool $clean_utf8 = false
12727
    ): string {
12728 1
        return self::ucfirst($str, $encoding, $clean_utf8);
12729
    }
12730
12731
    /**
12732
     * Uppercase for all words in the string.
12733
     *
12734
     * @param string   $str        <p>The input string.</p>
12735
     * @param string[] $exceptions [optional] <p>Exclusion for some words.</p>
12736
     * @param string   $char_list  [optional] <p>Additional chars that contains to words and do not start a new
12737
     *                             word.</p>
12738
     * @param string   $encoding   [optional] <p>Set the charset.</p>
12739
     * @param bool     $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
12740
     *
12741
     * @psalm-pure
12742
     *
12743
     * @return string
12744
     */
12745 8
    public static function ucwords(
12746
        string $str,
12747
        array $exceptions = [],
12748
        string $char_list = '',
12749
        string $encoding = 'UTF-8',
12750
        bool $clean_utf8 = false
12751
    ): string {
12752 8
        if (!$str) {
12753 2
            return '';
12754
        }
12755
12756
        // INFO: mb_convert_case($str, MB_CASE_TITLE);
12757
        // -> MB_CASE_TITLE didn't only uppercase the first letter, it also lowercase all other letters
12758
12759 7
        if ($clean_utf8) {
12760
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
12761
            // if invalid characters are found in $haystack before $needle
12762 1
            $str = self::clean($str);
12763
        }
12764
12765 7
        $use_php_default_functions = !(bool) ($char_list . \implode('', $exceptions));
12766
12767
        if (
12768 7
            $use_php_default_functions
12769
            &&
12770 7
            ASCII::is_ascii($str)
12771
        ) {
12772
            return \ucwords($str);
12773
        }
12774
12775 7
        $words = self::str_to_words($str, $char_list);
12776 7
        $use_exceptions = $exceptions !== [];
12777
12778 7
        $words_str = '';
12779 7
        foreach ($words as &$word) {
12780 7
            if (!$word) {
12781 7
                continue;
12782
            }
12783
12784
            if (
12785 7
                !$use_exceptions
12786
                ||
12787 7
                !\in_array($word, $exceptions, true)
12788
            ) {
12789 7
                $words_str .= self::ucfirst($word, $encoding);
12790
            } else {
12791 7
                $words_str .= $word;
12792
            }
12793
        }
12794
12795 7
        return $words_str;
12796
    }
12797
12798
    /**
12799
     * Multi decode HTML entity + fix urlencoded-win1252-chars.
12800
     *
12801
     * e.g:
12802
     * 'test+test'                     => 'test test'
12803
     * 'D&#252;sseldorf'               => 'Düsseldorf'
12804
     * 'D%FCsseldorf'                  => 'Düsseldorf'
12805
     * 'D&#xFC;sseldorf'               => 'Düsseldorf'
12806
     * 'D%26%23xFC%3Bsseldorf'         => 'Düsseldorf'
12807
     * 'Düsseldorf'                   => 'Düsseldorf'
12808
     * 'D%C3%BCsseldorf'               => 'Düsseldorf'
12809
     * 'D%C3%83%C2%BCsseldorf'         => 'Düsseldorf'
12810
     * 'D%25C3%2583%25C2%25BCsseldorf' => 'Düsseldorf'
12811
     *
12812
     * @param string $str          <p>The input string.</p>
12813
     * @param bool   $multi_decode <p>Decode as often as possible.</p>
12814
     *
12815
     * @psalm-pure
12816
     *
12817
     * @return string
12818
     */
12819 4
    public static function urldecode(string $str, bool $multi_decode = true): string
12820
    {
12821 4
        if ($str === '') {
12822 3
            return '';
12823
        }
12824
12825
        if (
12826 4
            \strpos($str, '&') === false
12827
            &&
12828 4
            \strpos($str, '%') === false
12829
            &&
12830 4
            \strpos($str, '+') === false
12831
            &&
12832 4
            \strpos($str, '\u') === false
12833
        ) {
12834 3
            return self::fix_simple_utf8($str);
12835
        }
12836
12837 4
        $str = self::urldecode_unicode_helper($str);
12838
12839 4
        if ($multi_decode) {
12840
            do {
12841 3
                $str_compare = $str;
12842
12843
                /**
12844
                 * @psalm-suppress PossiblyInvalidArgument
12845
                 */
12846 3
                $str = self::fix_simple_utf8(
12847 3
                    \urldecode(
12848 3
                        self::html_entity_decode(
12849 3
                            self::to_utf8($str),
12850 3
                            \ENT_QUOTES | \ENT_HTML5
12851
                        )
12852
                    )
12853
                );
12854 3
            } while ($str_compare !== $str);
12855
        } else {
12856
            /**
12857
             * @psalm-suppress PossiblyInvalidArgument
12858
             */
12859 1
            $str = self::fix_simple_utf8(
12860 1
                \urldecode(
12861 1
                    self::html_entity_decode(
12862 1
                        self::to_utf8($str),
12863 1
                        \ENT_QUOTES | \ENT_HTML5
12864
                    )
12865
                )
12866
            );
12867
        }
12868
12869 4
        return $str;
12870
    }
12871
12872
    /**
12873
     * Return a array with "urlencoded"-win1252 -> UTF-8
12874
     *
12875
     * @psalm-pure
12876
     *
12877
     * @return string[]
12878
     *
12879
     * @deprecated <p>please use the "UTF8::urldecode()" function to decode a string</p>
12880
     */
12881 2
    public static function urldecode_fix_win1252_chars(): array
12882
    {
12883
        return [
12884 2
            '%20' => ' ',
12885
            '%21' => '!',
12886
            '%22' => '"',
12887
            '%23' => '#',
12888
            '%24' => '$',
12889
            '%25' => '%',
12890
            '%26' => '&',
12891
            '%27' => "'",
12892
            '%28' => '(',
12893
            '%29' => ')',
12894
            '%2A' => '*',
12895
            '%2B' => '+',
12896
            '%2C' => ',',
12897
            '%2D' => '-',
12898
            '%2E' => '.',
12899
            '%2F' => '/',
12900
            '%30' => '0',
12901
            '%31' => '1',
12902
            '%32' => '2',
12903
            '%33' => '3',
12904
            '%34' => '4',
12905
            '%35' => '5',
12906
            '%36' => '6',
12907
            '%37' => '7',
12908
            '%38' => '8',
12909
            '%39' => '9',
12910
            '%3A' => ':',
12911
            '%3B' => ';',
12912
            '%3C' => '<',
12913
            '%3D' => '=',
12914
            '%3E' => '>',
12915
            '%3F' => '?',
12916
            '%40' => '@',
12917
            '%41' => 'A',
12918
            '%42' => 'B',
12919
            '%43' => 'C',
12920
            '%44' => 'D',
12921
            '%45' => 'E',
12922
            '%46' => 'F',
12923
            '%47' => 'G',
12924
            '%48' => 'H',
12925
            '%49' => 'I',
12926
            '%4A' => 'J',
12927
            '%4B' => 'K',
12928
            '%4C' => 'L',
12929
            '%4D' => 'M',
12930
            '%4E' => 'N',
12931
            '%4F' => 'O',
12932
            '%50' => 'P',
12933
            '%51' => 'Q',
12934
            '%52' => 'R',
12935
            '%53' => 'S',
12936
            '%54' => 'T',
12937
            '%55' => 'U',
12938
            '%56' => 'V',
12939
            '%57' => 'W',
12940
            '%58' => 'X',
12941
            '%59' => 'Y',
12942
            '%5A' => 'Z',
12943
            '%5B' => '[',
12944
            '%5C' => '\\',
12945
            '%5D' => ']',
12946
            '%5E' => '^',
12947
            '%5F' => '_',
12948
            '%60' => '`',
12949
            '%61' => 'a',
12950
            '%62' => 'b',
12951
            '%63' => 'c',
12952
            '%64' => 'd',
12953
            '%65' => 'e',
12954
            '%66' => 'f',
12955
            '%67' => 'g',
12956
            '%68' => 'h',
12957
            '%69' => 'i',
12958
            '%6A' => 'j',
12959
            '%6B' => 'k',
12960
            '%6C' => 'l',
12961
            '%6D' => 'm',
12962
            '%6E' => 'n',
12963
            '%6F' => 'o',
12964
            '%70' => 'p',
12965
            '%71' => 'q',
12966
            '%72' => 'r',
12967
            '%73' => 's',
12968
            '%74' => 't',
12969
            '%75' => 'u',
12970
            '%76' => 'v',
12971
            '%77' => 'w',
12972
            '%78' => 'x',
12973
            '%79' => 'y',
12974
            '%7A' => 'z',
12975
            '%7B' => '{',
12976
            '%7C' => '|',
12977
            '%7D' => '}',
12978
            '%7E' => '~',
12979
            '%7F' => '',
12980
            '%80' => '`',
12981
            '%81' => '',
12982
            '%82' => '‚',
12983
            '%83' => 'ƒ',
12984
            '%84' => '„',
12985
            '%85' => '…',
12986
            '%86' => '†',
12987
            '%87' => '‡',
12988
            '%88' => 'ˆ',
12989
            '%89' => '‰',
12990
            '%8A' => 'Š',
12991
            '%8B' => '‹',
12992
            '%8C' => 'Œ',
12993
            '%8D' => '',
12994
            '%8E' => 'Ž',
12995
            '%8F' => '',
12996
            '%90' => '',
12997
            '%91' => '‘',
12998
            '%92' => '’',
12999
            '%93' => '“',
13000
            '%94' => '”',
13001
            '%95' => '•',
13002
            '%96' => '–',
13003
            '%97' => '—',
13004
            '%98' => '˜',
13005
            '%99' => '™',
13006
            '%9A' => 'š',
13007
            '%9B' => '›',
13008
            '%9C' => 'œ',
13009
            '%9D' => '',
13010
            '%9E' => 'ž',
13011
            '%9F' => 'Ÿ',
13012
            '%A0' => '',
13013
            '%A1' => '¡',
13014
            '%A2' => '¢',
13015
            '%A3' => '£',
13016
            '%A4' => '¤',
13017
            '%A5' => '¥',
13018
            '%A6' => '¦',
13019
            '%A7' => '§',
13020
            '%A8' => '¨',
13021
            '%A9' => '©',
13022
            '%AA' => 'ª',
13023
            '%AB' => '«',
13024
            '%AC' => '¬',
13025
            '%AD' => '',
13026
            '%AE' => '®',
13027
            '%AF' => '¯',
13028
            '%B0' => '°',
13029
            '%B1' => '±',
13030
            '%B2' => '²',
13031
            '%B3' => '³',
13032
            '%B4' => '´',
13033
            '%B5' => 'µ',
13034
            '%B6' => '¶',
13035
            '%B7' => '·',
13036
            '%B8' => '¸',
13037
            '%B9' => '¹',
13038
            '%BA' => 'º',
13039
            '%BB' => '»',
13040
            '%BC' => '¼',
13041
            '%BD' => '½',
13042
            '%BE' => '¾',
13043
            '%BF' => '¿',
13044
            '%C0' => 'À',
13045
            '%C1' => 'Á',
13046
            '%C2' => 'Â',
13047
            '%C3' => 'Ã',
13048
            '%C4' => 'Ä',
13049
            '%C5' => 'Å',
13050
            '%C6' => 'Æ',
13051
            '%C7' => 'Ç',
13052
            '%C8' => 'È',
13053
            '%C9' => 'É',
13054
            '%CA' => 'Ê',
13055
            '%CB' => 'Ë',
13056
            '%CC' => 'Ì',
13057
            '%CD' => 'Í',
13058
            '%CE' => 'Î',
13059
            '%CF' => 'Ï',
13060
            '%D0' => 'Ð',
13061
            '%D1' => 'Ñ',
13062
            '%D2' => 'Ò',
13063
            '%D3' => 'Ó',
13064
            '%D4' => 'Ô',
13065
            '%D5' => 'Õ',
13066
            '%D6' => 'Ö',
13067
            '%D7' => '×',
13068
            '%D8' => 'Ø',
13069
            '%D9' => 'Ù',
13070
            '%DA' => 'Ú',
13071
            '%DB' => 'Û',
13072
            '%DC' => 'Ü',
13073
            '%DD' => 'Ý',
13074
            '%DE' => 'Þ',
13075
            '%DF' => 'ß',
13076
            '%E0' => 'à',
13077
            '%E1' => 'á',
13078
            '%E2' => 'â',
13079
            '%E3' => 'ã',
13080
            '%E4' => 'ä',
13081
            '%E5' => 'å',
13082
            '%E6' => 'æ',
13083
            '%E7' => 'ç',
13084
            '%E8' => 'è',
13085
            '%E9' => 'é',
13086
            '%EA' => 'ê',
13087
            '%EB' => 'ë',
13088
            '%EC' => 'ì',
13089
            '%ED' => 'í',
13090
            '%EE' => 'î',
13091
            '%EF' => 'ï',
13092
            '%F0' => 'ð',
13093
            '%F1' => 'ñ',
13094
            '%F2' => 'ò',
13095
            '%F3' => 'ó',
13096
            '%F4' => 'ô',
13097
            '%F5' => 'õ',
13098
            '%F6' => 'ö',
13099
            '%F7' => '÷',
13100
            '%F8' => 'ø',
13101
            '%F9' => 'ù',
13102
            '%FA' => 'ú',
13103
            '%FB' => 'û',
13104
            '%FC' => 'ü',
13105
            '%FD' => 'ý',
13106
            '%FE' => 'þ',
13107
            '%FF' => 'ÿ',
13108
        ];
13109
    }
13110
13111
    /**
13112
     * Decodes a UTF-8 string to ISO-8859-1.
13113
     *
13114
     * @param string $str             <p>The input string.</p>
13115
     * @param bool   $keep_utf8_chars
13116
     *
13117
     * @psalm-pure
13118
     *
13119
     * @return string
13120
     *
13121
     * @noinspection SuspiciousBinaryOperationInspection
13122
     */
13123 14
    public static function utf8_decode(string $str, bool $keep_utf8_chars = false): string
13124
    {
13125 14
        if ($str === '') {
13126 6
            return '';
13127
        }
13128
13129
        // save for later comparision
13130 14
        $str_backup = $str;
13131 14
        $len = \strlen($str);
13132
13133 14
        if (self::$ORD === null) {
13134
            self::$ORD = self::getData('ord');
13135
        }
13136
13137 14
        if (self::$CHR === null) {
13138
            self::$CHR = self::getData('chr');
13139
        }
13140
13141 14
        $no_char_found = '?';
13142
        /** @noinspection ForeachInvariantsInspection */
13143 14
        for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) {
13144 14
            switch ($str[$i] & "\xF0") {
13145 14
                case "\xC0":
13146 13
                case "\xD0":
13147 13
                    $c = (self::$ORD[$str[$i] & "\x1F"] << 6) | self::$ORD[$str[++$i] & "\x3F"];
13148 13
                    $str[$j] = $c < 256 ? self::$CHR[$c] : $no_char_found;
13149
13150 13
                    break;
13151
13152
                /** @noinspection PhpMissingBreakStatementInspection */
13153 13
                case "\xF0":
13154
                    ++$i;
13155
13156
                // no break
13157
13158 13
                case "\xE0":
13159 11
                    $str[$j] = $no_char_found;
13160 11
                    $i += 2;
13161
13162 11
                    break;
13163
13164
                default:
13165 12
                    $str[$j] = $str[$i];
13166
            }
13167
        }
13168
13169
        /** @var false|string $return - needed for PhpStan (stubs error) */
13170 14
        $return = \substr($str, 0, $j);
13171 14
        if ($return === false) {
13172
            $return = '';
13173
        }
13174
13175
        if (
13176 14
            $keep_utf8_chars
13177
            &&
13178 14
            (int) self::strlen($return) >= (int) self::strlen($str_backup)
13179
        ) {
13180 2
            return $str_backup;
13181
        }
13182
13183 14
        return $return;
13184
    }
13185
13186
    /**
13187
     * Encodes an ISO-8859-1 string to UTF-8.
13188
     *
13189
     * @param string $str <p>The input string.</p>
13190
     *
13191
     * @psalm-pure
13192
     *
13193
     * @return string
13194
     */
13195 16
    public static function utf8_encode(string $str): string
13196
    {
13197 16
        if ($str === '') {
13198 14
            return '';
13199
        }
13200
13201
        /** @var false|string $str - the polyfill maybe return false */
13202 16
        $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

13202
        $str = \utf8_encode(/** @scrutinizer ignore-type */ $str);
Loading history...
13203
13204
        /** @noinspection CallableParameterUseCaseInTypeContextInspection */
13205
        /** @psalm-suppress TypeDoesNotContainType */
13206 16
        if ($str === false) {
13207
            return '';
13208
        }
13209
13210 16
        return $str;
13211
    }
13212
13213
    /**
13214
     * fix -> utf8-win1252 chars
13215
     *
13216
     * @param string $str <p>The input string.</p>
13217
     *
13218
     * @psalm-pure
13219
     *
13220
     * @return string
13221
     *
13222
     * @deprecated <p>please use "UTF8::fix_simple_utf8()"</p>
13223
     */
13224 2
    public static function utf8_fix_win1252_chars(string $str): string
13225
    {
13226 2
        return self::fix_simple_utf8($str);
13227
    }
13228
13229
    /**
13230
     * Returns an array with all utf8 whitespace characters.
13231
     *
13232
     * @see http://www.bogofilter.org/pipermail/bogofilter/2003-March/001889.html
13233
     *
13234
     * @psalm-pure
13235
     *
13236
     * @return string[]
13237
     *                  An array with all known whitespace characters as values and the type of whitespace as keys
13238
     *                  as defined in above URL
13239
     */
13240 2
    public static function whitespace_table(): array
13241
    {
13242 2
        return self::$WHITESPACE_TABLE;
13243
    }
13244
13245
    /**
13246
     * Limit the number of words in a string.
13247
     *
13248
     * @param string $str        <p>The input string.</p>
13249
     * @param int    $limit      <p>The limit of words as integer.</p>
13250
     * @param string $str_add_on <p>Replacement for the striped string.</p>
13251
     *
13252
     * @psalm-pure
13253
     *
13254
     * @return string
13255
     */
13256 2
    public static function words_limit(
13257
        string $str,
13258
        int $limit = 100,
13259
        string $str_add_on = '…'
13260
    ): string {
13261 2
        if ($str === '' || $limit < 1) {
13262 2
            return '';
13263
        }
13264
13265 2
        \preg_match('/^\\s*+(?:[^\\s]++\\s*+){1,' . $limit . '}/u', $str, $matches);
13266
13267
        if (
13268 2
            !isset($matches[0])
13269
            ||
13270 2
            \mb_strlen($str) === (int) \mb_strlen($matches[0])
13271
        ) {
13272 2
            return $str;
13273
        }
13274
13275 2
        return \rtrim($matches[0]) . $str_add_on;
13276
    }
13277
13278
    /**
13279
     * Wraps a string to a given number of characters
13280
     *
13281
     * @see http://php.net/manual/en/function.wordwrap.php
13282
     *
13283
     * @param string $str   <p>The input string.</p>
13284
     * @param int    $width [optional] <p>The column width.</p>
13285
     * @param string $break [optional] <p>The line is broken using the optional break parameter.</p>
13286
     * @param bool   $cut   [optional] <p>
13287
     *                      If the cut is set to true, the string is
13288
     *                      always wrapped at or before the specified width. So if you have
13289
     *                      a word that is larger than the given width, it is broken apart.
13290
     *                      </p>
13291
     *
13292
     * @psalm-pure
13293
     *
13294
     * @return string
13295
     *                <p>The given string wrapped at the specified column.</p>
13296
     */
13297 12
    public static function wordwrap(
13298
        string $str,
13299
        int $width = 75,
13300
        string $break = "\n",
13301
        bool $cut = false
13302
    ): string {
13303 12
        if ($str === '' || $break === '') {
13304 4
            return '';
13305
        }
13306
13307 10
        $str_split = \explode($break, $str);
13308 10
        if ($str_split === false) {
13309
            return '';
13310
        }
13311
13312 10
        $chars = [];
13313 10
        $word_split = '';
13314 10
        foreach ($str_split as $i => $i_value) {
13315 10
            if ($i) {
13316 3
                $chars[] = $break;
13317 3
                $word_split .= '#';
13318
            }
13319
13320 10
            foreach (self::str_split($i_value) as $c) {
13321 10
                $chars[] = $c;
13322 10
                if ($c === ' ') {
13323 3
                    $word_split .= ' ';
13324
                } else {
13325 10
                    $word_split .= '?';
13326
                }
13327
            }
13328
        }
13329
13330 10
        $str_return = '';
13331 10
        $j = 0;
13332 10
        $b = -1;
13333 10
        $i = -1;
13334 10
        $word_split = \wordwrap($word_split, $width, '#', $cut);
13335
13336 10
        $max = \mb_strlen($word_split);
13337 10
        while (($b = \mb_strpos($word_split, '#', $b + 1)) !== false) {
13338 8
            for (++$i; $i < $b; ++$i) {
13339 8
                $str_return .= $chars[$j];
13340 8
                unset($chars[$j++]);
13341
13342
                // prevent endless loop, e.g. if there is a error in the "mb_*" polyfill
13343 8
                if ($i > $max) {
13344
                    break 2;
13345
                }
13346
            }
13347
13348
            if (
13349 8
                $break === $chars[$j]
13350
                ||
13351 8
                $chars[$j] === ' '
13352
            ) {
13353 5
                unset($chars[$j++]);
13354
            }
13355
13356 8
            $str_return .= $break;
13357
13358
            // prevent endless loop, e.g. if there is a error in the "mb_*" polyfill
13359 8
            if ($b > $max) {
13360
                break;
13361
            }
13362
        }
13363
13364 10
        return $str_return . \implode('', $chars);
13365
    }
13366
13367
    /**
13368
     * Line-Wrap the string after $limit, but split the string by "$delimiter" before ...
13369
     *    ... so that we wrap the per line.
13370
     *
13371
     * @param string      $str             <p>The input string.</p>
13372
     * @param int         $width           [optional] <p>The column width.</p>
13373
     * @param string      $break           [optional] <p>The line is broken using the optional break parameter.</p>
13374
     * @param bool        $cut             [optional] <p>
13375
     *                                     If the cut is set to true, the string is
13376
     *                                     always wrapped at or before the specified width. So if you have
13377
     *                                     a word that is larger than the given width, it is broken apart.
13378
     *                                     </p>
13379
     * @param bool        $add_final_break [optional] <p>
13380
     *                                     If this flag is true, then the method will add a $break at the end
13381
     *                                     of the result string.
13382
     *                                     </p>
13383
     * @param string|null $delimiter       [optional] <p>
13384
     *                                     You can change the default behavior, where we split the string by newline.
13385
     *                                     </p>
13386
     *
13387
     * @psalm-pure
13388
     *
13389
     * @return string
13390
     */
13391 1
    public static function wordwrap_per_line(
13392
        string $str,
13393
        int $width = 75,
13394
        string $break = "\n",
13395
        bool $cut = false,
13396
        bool $add_final_break = true,
13397
        string $delimiter = null
13398
    ): string {
13399 1
        if ($delimiter === null) {
13400 1
            $strings = \preg_split('/\\r\\n|\\r|\\n/', $str);
13401
        } else {
13402 1
            $strings = \explode($delimiter, $str);
13403
        }
13404
13405 1
        $string_helper_array = [];
13406 1
        if ($strings !== false) {
13407 1
            foreach ($strings as $value) {
13408 1
                $string_helper_array[] = self::wordwrap($value, $width, $break, $cut);
13409
            }
13410
        }
13411
13412 1
        if ($add_final_break) {
13413 1
            $final_break = $break;
13414
        } else {
13415 1
            $final_break = '';
13416
        }
13417
13418 1
        return \implode($delimiter ?? "\n", $string_helper_array) . $final_break;
13419
    }
13420
13421
    /**
13422
     * Returns an array of Unicode White Space characters.
13423
     *
13424
     * @psalm-pure
13425
     *
13426
     * @return string[]
13427
     *                  <p>An array with numeric code point as key and White Space Character as value.</p>
13428
     */
13429 2
    public static function ws(): array
13430
    {
13431 2
        return self::$WHITESPACE;
13432
    }
13433
13434
    /**
13435
     * Checks whether the passed string contains only byte sequences that are valid UTF-8 characters.
13436
     *
13437
     * @see          http://hsivonen.iki.fi/php-utf8/
13438
     *
13439
     * @param string $str    <p>The string to be checked.</p>
13440
     * @param bool   $strict <p>Check also if the string is not UTF-16 or UTF-32.</p>
13441
     *
13442
     * @psalm-pure
13443
     *
13444
     * @return bool
13445
     *
13446
     * @noinspection ReturnTypeCanBeDeclaredInspection
13447
     */
13448 108
    private static function is_utf8_string(string $str, bool $strict = false)
13449
    {
13450 108
        if ($str === '') {
13451 14
            return true;
13452
        }
13453
13454 102
        if ($strict) {
13455 2
            $is_binary = self::is_binary($str, true);
13456
13457 2
            if ($is_binary && self::is_utf16($str, false) !== false) {
13458 2
                return false;
13459
            }
13460
13461
            if ($is_binary && self::is_utf32($str, false) !== false) {
13462
                return false;
13463
            }
13464
        }
13465
13466 102
        if (!self::pcre_utf8_support()) {
13467
            // If even just the first character can be matched, when the /u
13468
            // modifier is used, then it's valid UTF-8. If the UTF-8 is somehow
13469
            // invalid, nothing at all will match, even if the string contains
13470
            // some valid sequences
13471
            return \preg_match('/^./us', $str, $ar) === 1;
13472
        }
13473
13474 102
        $mState = 0; // cached expected number of octets after the current octet
13475
        // until the beginning of the next UTF8 character sequence
13476 102
        $mUcs4 = 0; // cached Unicode character
13477 102
        $mBytes = 1; // cached expected number of octets in the current sequence
13478
13479 102
        if (self::$ORD === null) {
13480
            self::$ORD = self::getData('ord');
13481
        }
13482
13483 102
        $len = \strlen($str);
13484
        /** @noinspection ForeachInvariantsInspection */
13485 102
        for ($i = 0; $i < $len; ++$i) {
13486 102
            $in = self::$ORD[$str[$i]];
13487
13488 102
            if ($mState === 0) {
13489
                // When mState is zero we expect either a US-ASCII character or a
13490
                // multi-octet sequence.
13491 102
                if ((0x80 & $in) === 0) {
13492
                    // US-ASCII, pass straight through.
13493 97
                    $mBytes = 1;
13494 83
                } elseif ((0xE0 & $in) === 0xC0) {
13495
                    // First octet of 2 octet sequence.
13496 73
                    $mUcs4 = $in;
13497 73
                    $mUcs4 = ($mUcs4 & 0x1F) << 6;
13498 73
                    $mState = 1;
13499 73
                    $mBytes = 2;
13500 58
                } elseif ((0xF0 & $in) === 0xE0) {
13501
                    // First octet of 3 octet sequence.
13502 42
                    $mUcs4 = $in;
13503 42
                    $mUcs4 = ($mUcs4 & 0x0F) << 12;
13504 42
                    $mState = 2;
13505 42
                    $mBytes = 3;
13506 29
                } elseif ((0xF8 & $in) === 0xF0) {
13507
                    // First octet of 4 octet sequence.
13508 18
                    $mUcs4 = $in;
13509 18
                    $mUcs4 = ($mUcs4 & 0x07) << 18;
13510 18
                    $mState = 3;
13511 18
                    $mBytes = 4;
13512 13
                } elseif ((0xFC & $in) === 0xF8) {
13513
                    /* First octet of 5 octet sequence.
13514
                     *
13515
                     * This is illegal because the encoded codepoint must be either
13516
                     * (a) not the shortest form or
13517
                     * (b) outside the Unicode range of 0-0x10FFFF.
13518
                     * Rather than trying to resynchronize, we will carry on until the end
13519
                     * of the sequence and let the later error handling code catch it.
13520
                     */
13521 5
                    $mUcs4 = $in;
13522 5
                    $mUcs4 = ($mUcs4 & 0x03) << 24;
13523 5
                    $mState = 4;
13524 5
                    $mBytes = 5;
13525 10
                } elseif ((0xFE & $in) === 0xFC) {
13526
                    // First octet of 6 octet sequence, see comments for 5 octet sequence.
13527 5
                    $mUcs4 = $in;
13528 5
                    $mUcs4 = ($mUcs4 & 1) << 30;
13529 5
                    $mState = 5;
13530 5
                    $mBytes = 6;
13531
                } else {
13532
                    // Current octet is neither in the US-ASCII range nor a legal first
13533
                    // octet of a multi-octet sequence.
13534 102
                    return false;
13535
                }
13536 83
            } elseif ((0xC0 & $in) === 0x80) {
13537
13538
                // When mState is non-zero, we expect a continuation of the multi-octet
13539
                // sequence
13540
13541
                // Legal continuation.
13542 75
                $shift = ($mState - 1) * 6;
13543 75
                $tmp = $in;
13544 75
                $tmp = ($tmp & 0x0000003F) << $shift;
13545 75
                $mUcs4 |= $tmp;
13546
                // Prefix: End of the multi-octet sequence. mUcs4 now contains the final
13547
                // Unicode code point to be output.
13548 75
                if (--$mState === 0) {
13549
                    // Check for illegal sequences and code points.
13550
                    //
13551
                    // From Unicode 3.1, non-shortest form is illegal
13552
                    if (
13553 75
                        ($mBytes === 2 && $mUcs4 < 0x0080)
13554
                        ||
13555 75
                        ($mBytes === 3 && $mUcs4 < 0x0800)
13556
                        ||
13557 75
                        ($mBytes === 4 && $mUcs4 < 0x10000)
13558
                        ||
13559 75
                        ($mBytes > 4)
13560
                        ||
13561
                        // From Unicode 3.2, surrogate characters are illegal.
13562 75
                        (($mUcs4 & 0xFFFFF800) === 0xD800)
13563
                        ||
13564
                        // Code points outside the Unicode range are illegal.
13565 75
                        ($mUcs4 > 0x10FFFF)
13566
                    ) {
13567 9
                        return false;
13568
                    }
13569
                    // initialize UTF8 cache
13570 75
                    $mState = 0;
13571 75
                    $mUcs4 = 0;
13572 75
                    $mBytes = 1;
13573
                }
13574
            } else {
13575
                // ((0xC0 & (*in) != 0x80) && (mState != 0))
13576
                // Incomplete multi-octet sequence.
13577 35
                return false;
13578
            }
13579
        }
13580
13581 67
        return true;
13582
    }
13583
13584
    /**
13585
     * @param string $str
13586
     * @param bool   $use_lowercase      <p>Use uppercase by default, otherwise use lowercase.</p>
13587
     * @param bool   $use_full_case_fold <p>Convert not only common cases.</p>
13588
     *
13589
     * @psalm-pure
13590
     *
13591
     * @return string
13592
     *
13593
     * @noinspection ReturnTypeCanBeDeclaredInspection
13594
     */
13595 33
    private static function fixStrCaseHelper(
13596
        string $str,
13597
        bool $use_lowercase = false,
13598
        bool $use_full_case_fold = false
13599
    ) {
13600 33
        $upper = self::$COMMON_CASE_FOLD['upper'];
13601 33
        $lower = self::$COMMON_CASE_FOLD['lower'];
13602
13603 33
        if ($use_lowercase) {
13604 2
            $str = \str_replace(
13605 2
                $upper,
13606 2
                $lower,
13607 2
                $str
13608
            );
13609
        } else {
13610 31
            $str = \str_replace(
13611 31
                $lower,
13612 31
                $upper,
13613 31
                $str
13614
            );
13615
        }
13616
13617 33
        if ($use_full_case_fold) {
13618
            /**
13619
             * @psalm-suppress ImpureStaticVariable
13620
             *
13621
             * @var array<mixed>|null
13622
             */
13623 31
            static $FULL_CASE_FOLD = null;
13624 31
            if ($FULL_CASE_FOLD === null) {
13625 1
                $FULL_CASE_FOLD = self::getData('caseFolding_full');
13626
            }
13627
13628 31
            if ($use_lowercase) {
13629 2
                $str = \str_replace($FULL_CASE_FOLD[0], $FULL_CASE_FOLD[1], $str);
13630
            } else {
13631 29
                $str = \str_replace($FULL_CASE_FOLD[1], $FULL_CASE_FOLD[0], $str);
13632
            }
13633
        }
13634
13635 33
        return $str;
13636
    }
13637
13638
    /**
13639
     * get data from "/data/*.php"
13640
     *
13641
     * @param string $file
13642
     *
13643
     * @psalm-pure
13644
     *
13645
     * @return array
13646
     *
13647
     * @noinspection ReturnTypeCanBeDeclaredInspection
13648
     */
13649 6
    private static function getData(string $file)
13650
    {
13651
        /** @noinspection PhpIncludeInspection */
13652
        /** @noinspection UsingInclusionReturnValueInspection */
13653
        /** @psalm-suppress UnresolvableInclude */
13654 6
        return include __DIR__ . '/data/' . $file . '.php';
13655
    }
13656
13657
    /**
13658
     * @psalm-pure
13659
     *
13660
     * @return true|null
13661
     */
13662 12
    private static function initEmojiData()
13663
    {
13664 12
        if (self::$EMOJI_KEYS_CACHE === null) {
13665 1
            if (self::$EMOJI === null) {
13666 1
                self::$EMOJI = self::getData('emoji');
13667
            }
13668
13669
            /**
13670
             * @psalm-suppress ImpureFunctionCall - static sort function is used
13671
             */
13672 1
            \uksort(
13673 1
                self::$EMOJI,
13674
                static function (string $a, string $b): int {
13675 1
                    return \strlen($b) <=> \strlen($a);
13676 1
                }
13677
            );
13678
13679 1
            self::$EMOJI_KEYS_CACHE = \array_keys(self::$EMOJI);
13680 1
            self::$EMOJI_VALUES_CACHE = self::$EMOJI;
13681
13682 1
            foreach (self::$EMOJI_KEYS_CACHE as $key) {
13683 1
                $tmp_key = \crc32($key);
13684 1
                self::$EMOJI_KEYS_REVERSIBLE_CACHE[] = '_-_PORTABLE_UTF8_-_' . $tmp_key . '_-_' . \strrev((string) $tmp_key) . '_-_8FTU_ELBATROP_-_';
13685
            }
13686
13687 1
            return true;
13688
        }
13689
13690 12
        return null;
13691
    }
13692
13693
    /**
13694
     * Checks whether mbstring "overloaded" is active on the server.
13695
     *
13696
     * @psalm-pure
13697
     *
13698
     * @return bool
13699
     *
13700
     * @noinspection ReturnTypeCanBeDeclaredInspection
13701
     */
13702
    private static function mbstring_overloaded()
13703
    {
13704
        /**
13705
         * INI directive 'mbstring.func_overload' is deprecated since PHP 7.2
13706
         */
13707
13708
        /** @noinspection PhpComposerExtensionStubsInspection */
13709
        /** @noinspection PhpUsageOfSilenceOperatorInspection */
13710
        return \defined('MB_OVERLOAD_STRING')
13711
               &&
13712
               ((int) @\ini_get('mbstring.func_overload') & \MB_OVERLOAD_STRING);
13713
    }
13714
13715
    /**
13716
     * @param array    $strings
13717
     * @param bool     $remove_empty_values
13718
     * @param int|null $remove_short_values
13719
     *
13720
     * @psalm-pure
13721
     *
13722
     * @return array
13723
     *
13724
     * @noinspection ReturnTypeCanBeDeclaredInspection
13725
     */
13726 2
    private static function reduce_string_array(
13727
        array $strings,
13728
        bool $remove_empty_values,
13729
        int $remove_short_values = null
13730
    ) {
13731
        // init
13732 2
        $return = [];
13733
13734 2
        foreach ($strings as &$str) {
13735
            if (
13736 2
                $remove_short_values !== null
13737
                &&
13738 2
                \mb_strlen($str) <= $remove_short_values
13739
            ) {
13740 2
                continue;
13741
            }
13742
13743
            if (
13744 2
                $remove_empty_values
13745
                &&
13746 2
                \trim($str) === ''
13747
            ) {
13748 2
                continue;
13749
            }
13750
13751 2
            $return[] = $str;
13752
        }
13753
13754 2
        return $return;
13755
    }
13756
13757
    /**
13758
     * rxClass
13759
     *
13760
     * @param string $s
13761
     * @param string $class
13762
     *
13763
     * @psalm-pure
13764
     *
13765
     * @return string
13766
     *
13767
     * @noinspection ReturnTypeCanBeDeclaredInspection
13768
     */
13769 33
    private static function rxClass(string $s, string $class = '')
13770
    {
13771
        /**
13772
         * @psalm-suppress ImpureStaticVariable
13773
         *
13774
         * @var array<string,string>
13775
         */
13776 33
        static $RX_CLASS_CACHE = [];
13777
13778 33
        $cache_key = $s . '_' . $class;
13779
13780 33
        if (isset($RX_CLASS_CACHE[$cache_key])) {
13781 21
            return $RX_CLASS_CACHE[$cache_key];
13782
        }
13783
13784 16
        $class_array = [$class];
13785
13786
        /** @noinspection SuspiciousLoopInspection */
13787
        /** @noinspection AlterInForeachInspection */
13788 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...
13789
            /** @var string $s */
13790 15
            if ($s === '-') {
13791
                $class_array[0] = '-' . $class_array[0];
13792 15
            } elseif (!isset($s[2])) {
13793 15
                $class_array[0] .= \preg_quote($s, '/');
13794 1
            } elseif (self::strlen($s) === 1) {
13795 1
                $class_array[0] .= $s;
13796
            } else {
13797 15
                $class_array[] = $s;
13798
            }
13799
        }
13800
13801 16
        if ($class_array[0]) {
13802 16
            $class_array[0] = '[' . $class_array[0] . ']';
13803
        }
13804
13805 16
        if (\count($class_array) === 1) {
13806 16
            $return = $class_array[0];
13807
        } else {
13808
            $return = '(?:' . \implode('|', $class_array) . ')';
13809
        }
13810
13811 16
        $RX_CLASS_CACHE[$cache_key] = $return;
13812
13813 16
        return $return;
13814
    }
13815
13816
    /**
13817
     * Personal names such as "Marcus Aurelius" are sometimes typed incorrectly using lowercase ("marcus aurelius").
13818
     *
13819
     * @param string $names
13820
     * @param string $delimiter
13821
     * @param string $encoding
13822
     *
13823
     * @psalm-pure
13824
     *
13825
     * @return string
13826
     *
13827
     * @noinspection ReturnTypeCanBeDeclaredInspection
13828
     */
13829 1
    private static function str_capitalize_name_helper(
13830
        string $names,
13831
        string $delimiter,
13832
        string $encoding = 'UTF-8'
13833
    ) {
13834
        // init
13835 1
        $name_helper_array = \explode($delimiter, $names);
13836 1
        if ($name_helper_array === false) {
13837
            return '';
13838
        }
13839
13840
        $special_cases = [
13841 1
            'names' => [
13842
                'ab',
13843
                'af',
13844
                'al',
13845
                'and',
13846
                'ap',
13847
                'bint',
13848
                'binte',
13849
                'da',
13850
                'de',
13851
                'del',
13852
                'den',
13853
                'der',
13854
                'di',
13855
                'dit',
13856
                'ibn',
13857
                'la',
13858
                'mac',
13859
                'nic',
13860
                'of',
13861
                'ter',
13862
                'the',
13863
                'und',
13864
                'van',
13865
                'von',
13866
                'y',
13867
                'zu',
13868
            ],
13869
            'prefixes' => [
13870
                'al-',
13871
                "d'",
13872
                'ff',
13873
                "l'",
13874
                'mac',
13875
                'mc',
13876
                'nic',
13877
            ],
13878
        ];
13879
13880 1
        foreach ($name_helper_array as &$name) {
13881 1
            if (\in_array($name, $special_cases['names'], true)) {
13882 1
                continue;
13883
            }
13884
13885 1
            $continue = false;
13886
13887 1
            if ($delimiter === '-') {
13888
                /** @noinspection AlterInForeachInspection */
13889 1
                foreach ((array) $special_cases['names'] as &$beginning) {
13890 1
                    if (self::strpos($name, $beginning, 0, $encoding) === 0) {
13891 1
                        $continue = true;
13892
13893 1
                        break;
13894
                    }
13895
                }
13896
            }
13897
13898
            /** @noinspection AlterInForeachInspection */
13899 1
            foreach ((array) $special_cases['prefixes'] as &$beginning) {
13900 1
                if (self::strpos($name, $beginning, 0, $encoding) === 0) {
13901 1
                    $continue = true;
13902
13903 1
                    break;
13904
                }
13905
            }
13906
13907 1
            if ($continue) {
13908 1
                continue;
13909
            }
13910
13911 1
            $name = self::ucfirst($name);
13912
        }
13913
13914 1
        return \implode($delimiter, $name_helper_array);
13915
    }
13916
13917
    /**
13918
     * Generic case-sensitive transformation for collation matching.
13919
     *
13920
     * @param string $str <p>The input string</p>
13921
     *
13922
     * @psalm-pure
13923
     *
13924
     * @return string|null
13925
     */
13926 6
    private static function strtonatfold(string $str)
13927
    {
13928
        /** @noinspection PhpUndefinedClassInspection */
13929 6
        return \preg_replace(
13930 6
            '/\p{Mn}+/u',
13931 6
            '',
13932 6
            \Normalizer::normalize($str, \Normalizer::NFD)
13933
        );
13934
    }
13935
13936
    /**
13937
     * @param int|string $input
13938
     *
13939
     * @psalm-pure
13940
     *
13941
     * @return string
13942
     *
13943
     * @noinspection ReturnTypeCanBeDeclaredInspection
13944
     * @noinspection SuspiciousBinaryOperationInspection
13945
     */
13946 31
    private static function to_utf8_convert_helper($input)
13947
    {
13948
        // init
13949 31
        $buf = '';
13950
13951 31
        if (self::$ORD === null) {
13952 1
            self::$ORD = self::getData('ord');
13953
        }
13954
13955 31
        if (self::$CHR === null) {
13956 1
            self::$CHR = self::getData('chr');
13957
        }
13958
13959 31
        if (self::$WIN1252_TO_UTF8 === null) {
13960 1
            self::$WIN1252_TO_UTF8 = self::getData('win1252_to_utf8');
13961
        }
13962
13963 31
        $ordC1 = self::$ORD[$input];
13964 31
        if (isset(self::$WIN1252_TO_UTF8[$ordC1])) { // found in Windows-1252 special cases
13965 31
            $buf .= self::$WIN1252_TO_UTF8[$ordC1];
13966
        } else {
13967
            /** @noinspection OffsetOperationsInspection */
13968 1
            $cc1 = self::$CHR[$ordC1 / 64] | "\xC0";
13969 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...
13970 1
            $buf .= $cc1 . $cc2;
13971
        }
13972
13973 31
        return $buf;
13974
    }
13975
13976
    /**
13977
     * @param string $str
13978
     *
13979
     * @psalm-pure
13980
     *
13981
     * @return string
13982
     *
13983
     * @noinspection ReturnTypeCanBeDeclaredInspection
13984
     */
13985 10
    private static function urldecode_unicode_helper(string $str)
13986
    {
13987 10
        $pattern = '/%u([0-9a-fA-F]{3,4})/';
13988 10
        if (\preg_match($pattern, $str)) {
13989 7
            $str = (string) \preg_replace($pattern, '&#x\\1;', $str);
13990
        }
13991
13992 10
        return $str;
13993
    }
13994
}
13995