Passed
Push — master ( ddd236...1ff025 )
by Lars
14:01 queued 02:36
created

UTF8   F

Complexity

Total Complexity 1691

Size/Duplication

Total Lines 12762
Duplicated Lines 0 %

Test Coverage

Coverage 78.84%

Importance

Changes 97
Bugs 51 Features 6
Metric Value
eloc 4335
c 97
b 51
f 6
dl 0
loc 12762
ccs 2858
cts 3625
cp 0.7884
rs 0.8
wmc 1691

298 Methods

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

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
final class UTF8
8
{
9
    /**
10
     * (CRLF|([ZWNJ-ZWJ]|T+|L*(LV?V+|LV|LVT)T*|L+|[^Control])[Extend]*|[Control])
11
     * This regular expression is a work around for http://bugs.exim.org/1279
12
     */
13
    const GRAPHEME_CLUSTER_RX = "(?:\r\n|(?:[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가개갸걔거게겨계고과괘괴교구궈궤귀규그긔기까깨꺄꺠꺼께껴꼐꼬꽈꽤꾀꾜꾸꿔꿰뀌뀨끄끠끼나내냐냬너네녀녜노놔놰뇌뇨누눠눼뉘뉴느늬니다대댜댸더데뎌뎨도돠돼되됴두둬뒈뒤듀드듸디따때땨떄떠떼뗘뗴또똬뙈뙤뚀뚜뚸뛔뛰뜌뜨띄띠라래랴럐러레려례로롸뢔뢰료루뤄뤠뤼류르릐리마매먀먜머메며몌모뫄뫠뫼묘무뭐뭬뮈뮤므믜미바배뱌뱨버베벼볘보봐봬뵈뵤부붜붸뷔뷰브븨비빠빼뺘뺴뻐뻬뼈뼤뽀뽜뽸뾔뾰뿌뿨쀄쀠쀼쁘쁴삐사새샤섀서세셔셰소솨쇄쇠쇼수숴쉐쉬슈스싀시싸쌔쌰썌써쎄쎠쎼쏘쏴쐐쐬쑈쑤쒀쒜쒸쓔쓰씌씨아애야얘어에여예오와왜외요우워웨위유으의이자재쟈쟤저제져졔조좌좨죄죠주줘줴쥐쥬즈즤지짜째쨔쨰쩌쩨쪄쪠쪼쫘쫴쬐쬬쭈쭤쮀쮜쮸쯔쯰찌차채챠챼처체쳐쳬초촤쵀최쵸추춰췌취츄츠츼치카캐캬컈커케켜켸코콰쾌쾨쿄쿠쿼퀘퀴큐크킈키타태탸턔터테텨톄토톼퇘퇴툐투퉈퉤튀튜트틔티파패퍄퍠퍼페펴폐포퐈퐤푀표푸풔풰퓌퓨프픠피하해햐햬허헤혀혜호화홰회효후훠훼휘휴흐희히]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Me}\x{09BE}\x{09D7}\x{0B3E}\x{0B57}\x{0BBE}\x{0BD7}\x{0CC2}\x{0CD5}\x{0CD6}\x{0D3E}\x{0D57}\x{0DCF}\x{0DDF}\x{200C}\x{200D}\x{1D165}\x{1D16E}-\x{1D172}]*|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}])";
14
15
    /**
16
     * Bom => Byte-Length
17
     *
18
     * INFO: https://en.wikipedia.org/wiki/Byte_order_mark
19
     *
20
     * @var array
21
     */
22
    private static $BOM = [
23
        "\xef\xbb\xbf"     => 3, // UTF-8 BOM
24
        ''              => 6, // UTF-8 BOM as "WINDOWS-1252" (one char has [maybe] more then one byte ...)
25
        "\x00\x00\xfe\xff" => 4, // UTF-32 (BE) BOM
26
        '  þÿ'             => 6, // UTF-32 (BE) BOM as "WINDOWS-1252"
27
        "\xff\xfe\x00\x00" => 4, // UTF-32 (LE) BOM
28
        'ÿþ  '             => 6, // UTF-32 (LE) BOM as "WINDOWS-1252"
29
        "\xfe\xff"         => 2, // UTF-16 (BE) BOM
30
        'þÿ'               => 4, // UTF-16 (BE) BOM as "WINDOWS-1252"
31
        "\xff\xfe"         => 2, // UTF-16 (LE) BOM
32
        'ÿþ'               => 4, // UTF-16 (LE) BOM as "WINDOWS-1252"
33
    ];
34
35
    /**
36
     * Numeric code point => UTF-8 Character
37
     *
38
     * url: http://www.w3schools.com/charsets/ref_utf_punctuation.asp
39
     *
40
     * @var array
41
     */
42
    private static $WHITESPACE = [
43
        // NUL Byte
44
        0 => "\x0",
45
        // Tab
46
        9 => "\x9",
47
        // New Line
48
        10 => "\xa",
49
        // Vertical Tab
50
        11 => "\xb",
51
        // Carriage Return
52
        13 => "\xd",
53
        // Ordinary Space
54
        32 => "\x20",
55
        // NO-BREAK SPACE
56
        160 => "\xc2\xa0",
57
        // OGHAM SPACE MARK
58
        5760 => "\xe1\x9a\x80",
59
        // MONGOLIAN VOWEL SEPARATOR
60
        6158 => "\xe1\xa0\x8e",
61
        // EN QUAD
62
        8192 => "\xe2\x80\x80",
63
        // EM QUAD
64
        8193 => "\xe2\x80\x81",
65
        // EN SPACE
66
        8194 => "\xe2\x80\x82",
67
        // EM SPACE
68
        8195 => "\xe2\x80\x83",
69
        // THREE-PER-EM SPACE
70
        8196 => "\xe2\x80\x84",
71
        // FOUR-PER-EM SPACE
72
        8197 => "\xe2\x80\x85",
73
        // SIX-PER-EM SPACE
74
        8198 => "\xe2\x80\x86",
75
        // FIGURE SPACE
76
        8199 => "\xe2\x80\x87",
77
        // PUNCTUATION SPACE
78
        8200 => "\xe2\x80\x88",
79
        // THIN SPACE
80
        8201 => "\xe2\x80\x89",
81
        //HAIR SPACE
82
        8202 => "\xe2\x80\x8a",
83
        // LINE SEPARATOR
84
        8232 => "\xe2\x80\xa8",
85
        // PARAGRAPH SEPARATOR
86
        8233 => "\xe2\x80\xa9",
87
        // NARROW NO-BREAK SPACE
88
        8239 => "\xe2\x80\xaf",
89
        // MEDIUM MATHEMATICAL SPACE
90
        8287 => "\xe2\x81\x9f",
91
        // HALFWIDTH HANGUL FILLER
92
        65440 => "\xef\xbe\xa0",
93
        // IDEOGRAPHIC SPACE
94
        12288 => "\xe3\x80\x80",
95
    ];
96
97
    /**
98
     * @var array
99
     */
100
    private static $WHITESPACE_TABLE = [
101
        'SPACE'                     => "\x20",
102
        'NO-BREAK SPACE'            => "\xc2\xa0",
103
        'OGHAM SPACE MARK'          => "\xe1\x9a\x80",
104
        'EN QUAD'                   => "\xe2\x80\x80",
105
        'EM QUAD'                   => "\xe2\x80\x81",
106
        'EN SPACE'                  => "\xe2\x80\x82",
107
        'EM SPACE'                  => "\xe2\x80\x83",
108
        'THREE-PER-EM SPACE'        => "\xe2\x80\x84",
109
        'FOUR-PER-EM SPACE'         => "\xe2\x80\x85",
110
        'SIX-PER-EM SPACE'          => "\xe2\x80\x86",
111
        'FIGURE SPACE'              => "\xe2\x80\x87",
112
        'PUNCTUATION SPACE'         => "\xe2\x80\x88",
113
        'THIN SPACE'                => "\xe2\x80\x89",
114
        'HAIR SPACE'                => "\xe2\x80\x8a",
115
        'LINE SEPARATOR'            => "\xe2\x80\xa8",
116
        'PARAGRAPH SEPARATOR'       => "\xe2\x80\xa9",
117
        'ZERO WIDTH SPACE'          => "\xe2\x80\x8b",
118
        'NARROW NO-BREAK SPACE'     => "\xe2\x80\xaf",
119
        'MEDIUM MATHEMATICAL SPACE' => "\xe2\x81\x9f",
120
        'IDEOGRAPHIC SPACE'         => "\xe3\x80\x80",
121
        'HALFWIDTH HANGUL FILLER'   => "\xef\xbe\xa0",
122
    ];
123
124
    /**
125
     * @var array{upper: string[], lower: string[]}
126
     */
127
    private static $COMMON_CASE_FOLD = [
128
        'upper' => [
129
            'µ',
130
            'ſ',
131
            "\xCD\x85",
132
            'ς',
133
            'ẞ',
134
            "\xCF\x90",
135
            "\xCF\x91",
136
            "\xCF\x95",
137
            "\xCF\x96",
138
            "\xCF\xB0",
139
            "\xCF\xB1",
140
            "\xCF\xB5",
141
            "\xE1\xBA\x9B",
142
            "\xE1\xBE\xBE",
143
        ],
144
        'lower' => [
145
            'μ',
146
            's',
147
            'ι',
148
            'σ',
149
            'ß',
150
            'β',
151
            'θ',
152
            'φ',
153
            'π',
154
            'κ',
155
            'ρ',
156
            'ε',
157
            "\xE1\xB9\xA1",
158
            'ι',
159
        ],
160
    ];
161
162
    /**
163
     * @var array
164
     */
165
    private static $SUPPORT = [];
166
167
    /**
168
     * @var array|null
169
     */
170
    private static $BROKEN_UTF8_FIX;
171
172
    /**
173
     * @var array|null
174
     */
175
    private static $WIN1252_TO_UTF8;
176
177
    /**
178
     * @var array|null
179
     */
180
    private static $INTL_TRANSLITERATOR_LIST;
181
182
    /**
183
     * @var array|null
184
     */
185
    private static $ENCODINGS;
186
187
    /**
188
     * @var array|null
189
     */
190
    private static $ORD;
191
192
    /**
193
     * @var array|null
194
     */
195
    private static $EMOJI;
196
197
    /**
198
     * @var array|null
199
     */
200
    private static $EMOJI_VALUES_CACHE;
201
202
    /**
203
     * @var array|null
204
     */
205
    private static $EMOJI_KEYS_CACHE;
206
207
    /**
208
     * @var array|null
209
     */
210
    private static $EMOJI_KEYS_REVERSIBLE_CACHE;
211
212
    /**
213
     * @var array|null
214
     */
215
    private static $CHR;
216
217
    /**
218
     * __construct()
219
     */
220 33
    public function __construct()
221
    {
222 33
    }
223
224
    /**
225
     * Return the character at the specified position: $str[1] like functionality.
226
     *
227
     * @param string $str      <p>A UTF-8 string.</p>
228
     * @param int    $pos      <p>The position of character to return.</p>
229
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
230
     *
231
     * @return string single multi-byte character
232
     */
233 3
    public static function access(string $str, int $pos, string $encoding = 'UTF-8'): string
234
    {
235 3
        if ($str === '' || $pos < 0) {
236 2
            return '';
237
        }
238
239 3
        if ($encoding === 'UTF-8') {
240 3
            return (string) \mb_substr($str, $pos, 1);
241
        }
242
243
        return (string) self::substr($str, $pos, 1, $encoding);
244
    }
245
246
    /**
247
     * Prepends UTF-8 BOM character to the string and returns the whole string.
248
     *
249
     * INFO: If BOM already existed there, the Input string is returned.
250
     *
251
     * @param string $str <p>The input string.</p>
252
     *
253
     * @return string the output string that contains BOM
254
     */
255 2
    public static function add_bom_to_string(string $str): string
256
    {
257 2
        if (self::string_has_bom($str) === false) {
258 2
            $str = self::bom() . $str;
259
        }
260
261 2
        return $str;
262
    }
263
264
    /**
265
     * Changes all keys in an array.
266
     *
267
     * @param array  $array    <p>The array to work on</p>
268
     * @param int    $case     [optional] <p> Either <strong>CASE_UPPER</strong><br>
269
     *                         or <strong>CASE_LOWER</strong> (default)</p>
270
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
271
     *
272
     * @return string[]
273
     *                  <p>An array with its keys lower- or uppercased.</p>
274
     */
275 2
    public static function array_change_key_case(
276
        array $array,
277
        int $case = \CASE_LOWER,
278
        string $encoding = 'UTF-8'
279
    ): array {
280
        if (
281 2
            $case !== \CASE_LOWER
282
            &&
283 2
            $case !== \CASE_UPPER
284
        ) {
285
            $case = \CASE_LOWER;
286
        }
287
288 2
        $return = [];
289 2
        foreach ($array as $key => &$value) {
290 2
            $key = $case === \CASE_LOWER
291 2
                ? self::strtolower((string) $key, $encoding)
292 2
                : self::strtoupper((string) $key, $encoding);
293
294 2
            $return[$key] = $value;
295
        }
296
297 2
        return $return;
298
    }
299
300
    /**
301
     * Returns the substring between $start and $end, if found, or an empty
302
     * string. An optional offset may be supplied from which to begin the
303
     * search for the start string.
304
     *
305
     * @param string $str
306
     * @param string $start    <p>Delimiter marking the start of the substring.</p>
307
     * @param string $end      <p>Delimiter marking the end of the substring.</p>
308
     * @param int    $offset   [optional] <p>Index from which to begin the search. Default: 0</p>
309
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
310
     *
311
     * @return string
312
     */
313 16
    public static function between(
314
        string $str,
315
        string $start,
316
        string $end,
317
        int $offset = 0,
318
        string $encoding = 'UTF-8'
319
    ): string {
320 16
        if ($encoding === 'UTF-8') {
321 8
            $start_position = \mb_strpos($str, $start, $offset);
322 8
            if ($start_position === false) {
323 1
                return '';
324
            }
325
326 7
            $substr_index = $start_position + (int) \mb_strlen($start);
327 7
            $end_position = \mb_strpos($str, $end, $substr_index);
328
            if (
329 7
                $end_position === false
330
                ||
331 7
                $end_position === $substr_index
332
            ) {
333 2
                return '';
334
            }
335
336 5
            return (string) \mb_substr($str, $substr_index, $end_position - $substr_index);
337
        }
338
339 8
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
340
341 8
        $start_position = self::strpos($str, $start, $offset, $encoding);
342 8
        if ($start_position === false) {
343 1
            return '';
344
        }
345
346 7
        $substr_index = $start_position + (int) self::strlen($start, $encoding);
347 7
        $end_position = self::strpos($str, $end, $substr_index, $encoding);
348
        if (
349 7
            $end_position === false
350
            ||
351 7
            $end_position === $substr_index
352
        ) {
353 2
            return '';
354
        }
355
356 5
        return (string) self::substr(
357 5
            $str,
358 5
            $substr_index,
359 5
            $end_position - $substr_index,
360 5
            $encoding
361
        );
362
    }
363
364
    /**
365
     * Convert binary into a string.
366
     *
367
     * @param mixed $bin 1|0
368
     *
369
     * @return string
370
     */
371 2
    public static function binary_to_str($bin): string
372
    {
373 2
        if (!isset($bin[0])) {
374
            return '';
375
        }
376
377 2
        $convert = \base_convert($bin, 2, 16);
378 2
        if ($convert === '0') {
379 1
            return '';
380
        }
381
382 2
        return \pack('H*', $convert);
383
    }
384
385
    /**
386
     * Returns the UTF-8 Byte Order Mark Character.
387
     *
388
     * INFO: take a look at UTF8::$bom for e.g. UTF-16 and UTF-32 BOM values
389
     *
390
     * @return string UTF-8 Byte Order Mark
391
     */
392 4
    public static function bom(): string
393
    {
394 4
        return "\xef\xbb\xbf";
395
    }
396
397
    /**
398
     * @alias of UTF8::chr_map()
399
     *
400
     * @param callable $callback
401
     * @param string   $str
402
     *
403
     * @return string[]
404
     *
405
     * @see UTF8::chr_map()
406
     */
407 2
    public static function callback($callback, string $str): array
408
    {
409 2
        return self::chr_map($callback, $str);
410
    }
411
412
    /**
413
     * Returns the character at $index, with indexes starting at 0.
414
     *
415
     * @param string $str      <p>The input string.</p>
416
     * @param int    $index    <p>Position of the character.</p>
417
     * @param string $encoding [optional] <p>Default is UTF-8</p>
418
     *
419
     * @return string the character at $index
420
     */
421 9
    public static function char_at(string $str, int $index, string $encoding = 'UTF-8'): string
422
    {
423 9
        if ($encoding === 'UTF-8') {
424 5
            return (string) \mb_substr($str, $index, 1);
425
        }
426
427 4
        return (string) self::substr($str, $index, 1, $encoding);
428
    }
429
430
    /**
431
     * Returns an array consisting of the characters in the string.
432
     *
433
     * @param string $str <p>The input string.</p>
434
     *
435
     * @return string[] an array of chars
436
     */
437 3
    public static function chars(string $str): array
438
    {
439 3
        return self::str_split($str);
440
    }
441
442
    /**
443
     * This method will auto-detect your server environment for UTF-8 support.
444
     *
445
     * @return true|null
446
     *
447
     * @internal <p>You don't need to run it manually, it will be triggered if it's needed.</p>
448
     */
449 5
    public static function checkForSupport()
450
    {
451 5
        if (!isset(self::$SUPPORT['already_checked_via_portable_utf8'])) {
452
            self::$SUPPORT['already_checked_via_portable_utf8'] = true;
453
454
            // http://php.net/manual/en/book.mbstring.php
455
            self::$SUPPORT['mbstring'] = self::mbstring_loaded();
456
            self::$SUPPORT['mbstring_func_overload'] = self::mbstring_overloaded();
457
            if (self::$SUPPORT['mbstring'] === true) {
458
                \mb_internal_encoding('UTF-8');
459
                /** @noinspection UnusedFunctionResultInspection */
460
                /** @noinspection PhpComposerExtensionStubsInspection */
461
                \mb_regex_encoding('UTF-8');
462
                self::$SUPPORT['mbstring_internal_encoding'] = 'UTF-8';
463
            }
464
465
            // http://php.net/manual/en/book.iconv.php
466
            self::$SUPPORT['iconv'] = self::iconv_loaded();
467
468
            // http://php.net/manual/en/book.intl.php
469
            self::$SUPPORT['intl'] = self::intl_loaded();
470
471
            // http://php.net/manual/en/class.intlchar.php
472
            self::$SUPPORT['intlChar'] = self::intlChar_loaded();
473
474
            // http://php.net/manual/en/book.ctype.php
475
            self::$SUPPORT['ctype'] = self::ctype_loaded();
476
477
            // http://php.net/manual/en/class.finfo.php
478
            self::$SUPPORT['finfo'] = self::finfo_loaded();
479
480
            // http://php.net/manual/en/book.json.php
481
            self::$SUPPORT['json'] = self::json_loaded();
482
483
            // http://php.net/manual/en/book.pcre.php
484
            self::$SUPPORT['pcre_utf8'] = self::pcre_utf8_support();
485
486
            self::$SUPPORT['symfony_polyfill_used'] = self::symfony_polyfill_used();
487
            if (self::$SUPPORT['symfony_polyfill_used'] === true) {
488
                \mb_internal_encoding('UTF-8');
489
                self::$SUPPORT['mbstring_internal_encoding'] = 'UTF-8';
490
            }
491
492
            return true;
493
        }
494
495 5
        return null;
496
    }
497
498
    /**
499
     * Generates a UTF-8 encoded character from the given code point.
500
     *
501
     * INFO: opposite to UTF8::ord()
502
     *
503
     * @param int|string $code_point <p>The code point for which to generate a character.</p>
504
     * @param string     $encoding   [optional] <p>Default is UTF-8</p>
505
     *
506
     * @return string|null multi-byte character, returns null on failure or empty input
507
     */
508 25
    public static function chr($code_point, string $encoding = 'UTF-8')
509
    {
510
        // init
511 25
        static $CHAR_CACHE = [];
512
513 25
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
514 5
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
515
        }
516
517
        if (
518 25
            $encoding !== 'UTF-8'
519
            &&
520 25
            $encoding !== 'ISO-8859-1'
521
            &&
522 25
            $encoding !== 'WINDOWS-1252'
523
            &&
524 25
            self::$SUPPORT['mbstring'] === false
525
        ) {
526
            \trigger_error('UTF8::chr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
527
        }
528
529 25
        $cache_key = $code_point . $encoding;
530 25
        if (isset($CHAR_CACHE[$cache_key]) === true) {
531 23
            return $CHAR_CACHE[$cache_key];
532
        }
533
534 14
        if ($code_point <= 127) { // use "simple"-char only until "\x80"
535
536 13
            if (self::$CHR === null) {
537
                self::$CHR = self::getData('chr');
538
            }
539
540
            /**
541
             * @psalm-suppress PossiblyNullArrayAccess
542
             */
543 13
            $chr = self::$CHR[$code_point];
544
545 13
            if ($encoding !== 'UTF-8') {
546 1
                $chr = self::encode($encoding, $chr);
547
            }
548
549 13
            return $CHAR_CACHE[$cache_key] = $chr;
550
        }
551
552
        //
553
        // fallback via "IntlChar"
554
        //
555
556 7
        if (self::$SUPPORT['intlChar'] === true) {
557
            /** @noinspection PhpComposerExtensionStubsInspection */
558 7
            $chr = \IntlChar::chr($code_point);
559
560 7
            if ($encoding !== 'UTF-8') {
561
                $chr = self::encode($encoding, $chr);
562
            }
563
564 7
            return $CHAR_CACHE[$cache_key] = $chr;
565
        }
566
567
        //
568
        // fallback via vanilla php
569
        //
570
571
        if (self::$CHR === null) {
572
            self::$CHR = self::getData('chr');
573
        }
574
575
        $code_point = (int) $code_point;
576
        if ($code_point <= 0x7F) {
577
            /**
578
             * @psalm-suppress PossiblyNullArrayAccess
579
             */
580
            $chr = self::$CHR[$code_point];
581
        } elseif ($code_point <= 0x7FF) {
582
            /**
583
             * @psalm-suppress PossiblyNullArrayAccess
584
             */
585
            $chr = self::$CHR[($code_point >> 6) + 0xC0] .
586
                   self::$CHR[($code_point & 0x3F) + 0x80];
587
        } elseif ($code_point <= 0xFFFF) {
588
            /**
589
             * @psalm-suppress PossiblyNullArrayAccess
590
             */
591
            $chr = self::$CHR[($code_point >> 12) + 0xE0] .
592
                   self::$CHR[(($code_point >> 6) & 0x3F) + 0x80] .
593
                   self::$CHR[($code_point & 0x3F) + 0x80];
594
        } else {
595
            /**
596
             * @psalm-suppress PossiblyNullArrayAccess
597
             */
598
            $chr = self::$CHR[($code_point >> 18) + 0xF0] .
599
                   self::$CHR[(($code_point >> 12) & 0x3F) + 0x80] .
600
                   self::$CHR[(($code_point >> 6) & 0x3F) + 0x80] .
601
                   self::$CHR[($code_point & 0x3F) + 0x80];
602
        }
603
604
        if ($encoding !== 'UTF-8') {
605
            $chr = self::encode($encoding, $chr);
606
        }
607
608
        return $CHAR_CACHE[$cache_key] = $chr;
609
    }
610
611
    /**
612
     * Applies callback to all characters of a string.
613
     *
614
     * @param callable $callback <p>The callback function.</p>
615
     * @param string   $str      <p>UTF-8 string to run callback on.</p>
616
     *
617
     * @return string[]
618
     *                  <p>The outcome of the callback, as array.</p>
619
     */
620 2
    public static function chr_map($callback, string $str): array
621
    {
622 2
        return \array_map(
623 2
            $callback,
624 2
            self::str_split($str)
625
        );
626
    }
627
628
    /**
629
     * Generates an array of byte length of each character of a Unicode string.
630
     *
631
     * 1 byte => U+0000  - U+007F
632
     * 2 byte => U+0080  - U+07FF
633
     * 3 byte => U+0800  - U+FFFF
634
     * 4 byte => U+10000 - U+10FFFF
635
     *
636
     * @param string $str <p>The original unicode string.</p>
637
     *
638
     * @return int[] an array of byte lengths of each character
639
     */
640 4
    public static function chr_size_list(string $str): array
641
    {
642 4
        if ($str === '') {
643 4
            return [];
644
        }
645
646 4
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
647
            return \array_map(
648
                static function (string $data): int {
649
                    // "mb_" is available if overload is used, so use it ...
650
                    return \mb_strlen($data, 'CP850'); // 8-BIT
651
                },
652
                self::str_split($str)
653
            );
654
        }
655
656 4
        return \array_map('\strlen', self::str_split($str));
657
    }
658
659
    /**
660
     * Get a decimal code representation of a specific character.
661
     *
662
     * @param string $char <p>The input character.</p>
663
     *
664
     * @return int
665
     */
666 4
    public static function chr_to_decimal(string $char): int
667
    {
668 4
        if (self::$SUPPORT['iconv'] === true) {
669 4
            $chr_tmp = \iconv('UTF-8', 'UCS-4LE', $char);
670 4
            if ($chr_tmp !== false) {
671
                /** @noinspection OffsetOperationsInspection */
672 4
                return \unpack('V', $chr_tmp)[1];
673
            }
674
        }
675
676
        $code = self::ord($char[0]);
677
        $bytes = 1;
678
679
        if (!($code & 0x80)) {
680
            // 0xxxxxxx
681
            return $code;
682
        }
683
684
        if (($code & 0xe0) === 0xc0) {
685
            // 110xxxxx
686
            $bytes = 2;
687
            $code &= ~0xc0;
688
        } elseif (($code & 0xf0) === 0xe0) {
689
            // 1110xxxx
690
            $bytes = 3;
691
            $code &= ~0xe0;
692
        } elseif (($code & 0xf8) === 0xf0) {
693
            // 11110xxx
694
            $bytes = 4;
695
            $code &= ~0xf0;
696
        }
697
698
        for ($i = 2; $i <= $bytes; ++$i) {
699
            // 10xxxxxx
700
            $code = ($code << 6) + (self::ord($char[$i - 1]) & ~0x80);
701
        }
702
703
        return $code;
704
    }
705
706
    /**
707
     * Get hexadecimal code point (U+xxxx) of a UTF-8 encoded character.
708
     *
709
     * @param int|string $char   <p>The input character</p>
710
     * @param string     $prefix [optional]
711
     *
712
     * @return string The code point encoded as U+xxxx
713
     */
714 2
    public static function chr_to_hex($char, string $prefix = 'U+'): string
715
    {
716 2
        if ($char === '') {
717 2
            return '';
718
        }
719
720 2
        if ($char === '&#0;') {
721 2
            $char = '';
722
        }
723
724 2
        return self::int_to_hex(self::ord((string) $char), $prefix);
725
    }
726
727
    /**
728
     * alias for "UTF8::chr_to_decimal()"
729
     *
730
     * @param string $chr
731
     *
732
     * @return int
733
     *
734
     * @see UTF8::chr_to_decimal()
735
     * @deprecated <p>please use "UTF8::chr_to_decimal()"</p>
736
     */
737 2
    public static function chr_to_int(string $chr): int
738
    {
739 2
        return self::chr_to_decimal($chr);
740
    }
741
742
    /**
743
     * Splits a string into smaller chunks and multiple lines, using the specified line ending character.
744
     *
745
     * @param string $body         <p>The original string to be split.</p>
746
     * @param int    $chunk_length [optional] <p>The maximum character length of a chunk.</p>
747
     * @param string $end          [optional] <p>The character(s) to be inserted at the end of each chunk.</p>
748
     *
749
     * @return string the chunked string
750
     */
751 4
    public static function chunk_split(string $body, int $chunk_length = 76, string $end = "\r\n"): string
752
    {
753 4
        return \implode($end, self::str_split($body, $chunk_length));
754
    }
755
756
    /**
757
     * Accepts a string and removes all non-UTF-8 characters from it + extras if needed.
758
     *
759
     * @param string $str                           <p>The string to be sanitized.</p>
760
     * @param bool   $remove_bom                    [optional] <p>Set to true, if you need to remove UTF-BOM.</p>
761
     * @param bool   $normalize_whitespace          [optional] <p>Set to true, if you need to normalize the
762
     *                                              whitespace.</p>
763
     * @param bool   $normalize_msword              [optional] <p>Set to true, if you need to normalize MS Word chars
764
     *                                              e.g.: "…"
765
     *                                              => "..."</p>
766
     * @param bool   $keep_non_breaking_space       [optional] <p>Set to true, to keep non-breaking-spaces, in
767
     *                                              combination with
768
     *                                              $normalize_whitespace</p>
769
     * @param bool   $replace_diamond_question_mark [optional] <p>Set to true, if you need to remove diamond question
770
     *                                              mark e.g.: "�"</p>
771
     * @param bool   $remove_invisible_characters   [optional] <p>Set to false, if you not want to remove invisible
772
     *                                              characters e.g.: "\0"</p>
773
     *
774
     * @return string clean UTF-8 encoded string
775
     */
776 87
    public static function clean(
777
        string $str,
778
        bool $remove_bom = false,
779
        bool $normalize_whitespace = false,
780
        bool $normalize_msword = false,
781
        bool $keep_non_breaking_space = false,
782
        bool $replace_diamond_question_mark = false,
783
        bool $remove_invisible_characters = true
784
    ): string {
785
        // http://stackoverflow.com/questions/1401317/remove-non-utf8-characters-from-string
786
        // caused connection reset problem on larger strings
787
788 87
        $regex = '/
789
          (
790
            (?: [\x00-\x7F]               # single-byte sequences   0xxxxxxx
791
            |   [\xC0-\xDF][\x80-\xBF]    # double-byte sequences   110xxxxx 10xxxxxx
792
            |   [\xE0-\xEF][\x80-\xBF]{2} # triple-byte sequences   1110xxxx 10xxxxxx * 2
793
            |   [\xF0-\xF7][\x80-\xBF]{3} # quadruple-byte sequence 11110xxx 10xxxxxx * 3
794
            ){1,100}                      # ...one or more times
795
          )
796
        | ( [\x80-\xBF] )                 # invalid byte in range 10000000 - 10111111
797
        | ( [\xC0-\xFF] )                 # invalid byte in range 11000000 - 11111111
798
        /x';
799
        /** @noinspection NotOptimalRegularExpressionsInspection */
800 87
        $str = (string) \preg_replace($regex, '$1', $str);
801
802 87
        if ($replace_diamond_question_mark === true) {
803 33
            $str = self::replace_diamond_question_mark($str, '');
804
        }
805
806 87
        if ($remove_invisible_characters === true) {
807 87
            $str = self::remove_invisible_characters($str);
808
        }
809
810 87
        if ($normalize_whitespace === true) {
811 37
            $str = self::normalize_whitespace($str, $keep_non_breaking_space);
812
        }
813
814 87
        if ($normalize_msword === true) {
815 4
            $str = self::normalize_msword($str);
816
        }
817
818 87
        if ($remove_bom === true) {
819 37
            $str = self::remove_bom($str);
820
        }
821
822 87
        return $str;
823
    }
824
825
    /**
826
     * Clean-up a string and show only printable UTF-8 chars at the end  + fix UTF-8 encoding.
827
     *
828
     * @param string $str <p>The input string.</p>
829
     *
830
     * @return string
831
     */
832 33
    public static function cleanup($str): string
833
    {
834
        // init
835 33
        $str = (string) $str;
836
837 33
        if ($str === '') {
838 5
            return '';
839
        }
840
841
        // fixed ISO <-> UTF-8 Errors
842 33
        $str = self::fix_simple_utf8($str);
843
844
        // remove all none UTF-8 symbols
845
        // && remove diamond question mark (�)
846
        // && remove remove invisible characters (e.g. "\0")
847
        // && remove BOM
848
        // && normalize whitespace chars (but keep non-breaking-spaces)
849 33
        return self::clean(
850 33
            $str,
851 33
            true,
852 33
            true,
853 33
            false,
854 33
            true,
855 33
            true,
856 33
            true
857
        );
858
    }
859
860
    /**
861
     * Accepts a string or a array of strings and returns an array of Unicode code points.
862
     *
863
     * INFO: opposite to UTF8::string()
864
     *
865
     * @param string|string[] $arg     <p>A UTF-8 encoded string or an array of such strings.</p>
866
     * @param bool            $u_style <p>If True, will return code points in U+xxxx format,
867
     *                                 default, code points will be returned as integers.</p>
868
     *
869
     * @return array<int|string>
870
     *                           The array of code points:<br>
871
     *                           array<int> for $u_style === false<br>
872
     *                           array<string> for $u_style === true<br>
873
     */
874 12
    public static function codepoints($arg, bool $u_style = false): array
875
    {
876 12
        if (\is_string($arg) === true) {
877 12
            $arg = self::str_split($arg);
878
        }
879
880 12
        if (!\is_array($arg)) {
0 ignored issues
show
introduced by
The condition is_array($arg) is always true.
Loading history...
881 4
            return [];
882
        }
883
884 12
        if ($arg === []) {
885 7
            return [];
886
        }
887
888 11
        $arg = \array_map(
889
            [
890 11
                self::class,
891
                'ord',
892
            ],
893 11
            $arg
894
        );
895
896 11
        if ($u_style === true) {
897 2
            $arg = \array_map(
898
                [
899 2
                    self::class,
900
                    'int_to_hex',
901
                ],
902 2
                $arg
903
            );
904
        }
905
906 11
        return $arg;
907
    }
908
909
    /**
910
     * Trims the string and replaces consecutive whitespace characters with a
911
     * single space. This includes tabs and newline characters, as well as
912
     * multibyte whitespace such as the thin space and ideographic space.
913
     *
914
     * @param string $str <p>The input string.</p>
915
     *
916
     * @return string string with a trimmed $str and condensed whitespace
917
     */
918 13
    public static function collapse_whitespace(string $str): string
919
    {
920 13
        if (self::$SUPPORT['mbstring'] === true) {
921
            /** @noinspection PhpComposerExtensionStubsInspection */
922 13
            return \trim((string) \mb_ereg_replace('[[:space:]]+', ' ', $str));
923
        }
924
925
        return \trim(self::regex_replace($str, '[[:space:]]+', ' '));
926
    }
927
928
    /**
929
     * Returns count of characters used in a string.
930
     *
931
     * @param string $str                     <p>The input string.</p>
932
     * @param bool   $clean_utf8              [optional] <p>Remove non UTF-8 chars from the string.</p>
933
     * @param bool   $try_to_use_mb_functions [optional] <p>Set to false, if you don't want to use
934
     *
935
     * @return int[] an associative array of Character as keys and
936
     *               their count as values
937
     */
938 19
    public static function count_chars(
939
        string $str,
940
        bool $clean_utf8 = false,
941
        bool $try_to_use_mb_functions = true
942
    ): array {
943 19
        return \array_count_values(
944 19
            self::str_split(
945 19
                $str,
946 19
                1,
947 19
                $clean_utf8,
948 19
                $try_to_use_mb_functions
949
            )
950
        );
951
    }
952
953
    /**
954
     * Remove css media-queries.
955
     *
956
     * @param string $str
957
     *
958
     * @return string
959
     */
960 1
    public static function css_stripe_media_queries(string $str): string
961
    {
962 1
        return (string) \preg_replace(
963 1
            '#@media\\s+(?:only\\s)?(?:[\\s{(]|screen|all)\\s?[^{]+{.*}\\s*}\\s*#isumU',
964 1
            '',
965 1
            $str
966
        );
967
    }
968
969
    /**
970
     * Checks whether ctype is available on the server.
971
     *
972
     * @return bool
973
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
974
     */
975
    public static function ctype_loaded(): bool
976
    {
977
        return \extension_loaded('ctype');
978
    }
979
980
    /**
981
     * Converts an int value into a UTF-8 character.
982
     *
983
     * @param mixed $int
984
     *
985
     * @return string
986
     */
987 19
    public static function decimal_to_chr($int): string
988
    {
989 19
        return self::html_entity_decode('&#' . $int . ';', \ENT_QUOTES | \ENT_HTML5);
990
    }
991
992
    /**
993
     * Decodes a MIME header field
994
     *
995
     * @param string $str
996
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
997
     *
998
     * @return false|string
999
     *                      A decoded MIME field on success,
1000
     *                      or false if an error occurs during the decoding
1001
     */
1002
    public static function decode_mimeheader($str, string $encoding = 'UTF-8')
1003
    {
1004
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
1005
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
1006
        }
1007
1008
        if (self::$SUPPORT['iconv'] === true) {
1009
            return \iconv_mime_decode($str, \ICONV_MIME_DECODE_CONTINUE_ON_ERROR, $encoding);
1010
        }
1011
1012
        if ($encoding !== 'UTF-8') {
1013
            $str = self::encode($encoding, $str);
1014
        }
1015
1016
        return \mb_decode_mimeheader($str);
1017
    }
1018
1019
    /**
1020
     * Decodes a string which was encoded by "UTF8::emoji_encode()".
1021
     *
1022
     * @param string $str                            <p>The input string.</p>
1023
     * @param bool   $use_reversible_string_mappings [optional] <p>
1024
     *                                               When <b>TRUE</b>, we se a reversible string mapping
1025
     *                                               between "emoji_encode" and "emoji_decode".</p>
1026
     *
1027
     * @return string
1028
     */
1029 9
    public static function emoji_decode(
1030
        string $str,
1031
        bool $use_reversible_string_mappings = false
1032
    ): string {
1033 9
        self::initEmojiData();
1034
1035 9
        if ($use_reversible_string_mappings === true) {
1036 9
            return (string) \str_replace(
1037 9
                (array) self::$EMOJI_KEYS_REVERSIBLE_CACHE,
1038 9
                (array) self::$EMOJI_VALUES_CACHE,
1039 9
                $str
1040
            );
1041
        }
1042
1043 1
        return (string) \str_replace(
1044 1
            (array) self::$EMOJI_KEYS_CACHE,
1045 1
            (array) self::$EMOJI_VALUES_CACHE,
1046 1
            $str
1047
        );
1048
    }
1049
1050
    /**
1051
     * Encode a string with emoji chars into a non-emoji string.
1052
     *
1053
     * @param string $str                            <p>The input string</p>
1054
     * @param bool   $use_reversible_string_mappings [optional] <p>
1055
     *                                               when <b>TRUE</b>, we se a reversible string mapping
1056
     *                                               between "emoji_encode" and "emoji_decode"</p>
1057
     *
1058
     * @return string
1059
     */
1060 12
    public static function emoji_encode(
1061
        string $str,
1062
        bool $use_reversible_string_mappings = false
1063
    ): string {
1064 12
        self::initEmojiData();
1065
1066 12
        if ($use_reversible_string_mappings === true) {
1067 9
            return (string) \str_replace(
1068 9
                (array) self::$EMOJI_VALUES_CACHE,
1069 9
                (array) self::$EMOJI_KEYS_REVERSIBLE_CACHE,
1070 9
                $str
1071
            );
1072
        }
1073
1074 4
        return (string) \str_replace(
1075 4
            (array) self::$EMOJI_VALUES_CACHE,
1076 4
            (array) self::$EMOJI_KEYS_CACHE,
1077 4
            $str
1078
        );
1079
    }
1080
1081
    /**
1082
     * Encode a string with a new charset-encoding.
1083
     *
1084
     * INFO:  This function will also try to fix broken / double encoding,
1085
     *        so you can call this function also on a UTF-8 string and you don't mess up the string.
1086
     *
1087
     * @param string $to_encoding                   <p>e.g. 'UTF-16', 'UTF-8', 'ISO-8859-1', etc.</p>
1088
     * @param string $str                           <p>The input string</p>
1089
     * @param bool   $auto_detect_the_from_encoding [optional] <p>Force the new encoding (we try to fix broken / double
1090
     *                                              encoding for UTF-8)<br> otherwise we auto-detect the current
1091
     *                                              string-encoding</p>
1092
     * @param string $from_encoding                 [optional] <p>e.g. 'UTF-16', 'UTF-8', 'ISO-8859-1', etc.<br>
1093
     *                                              A empty string will trigger the autodetect anyway.</p>
1094
     *
1095
     * @return string
1096
     *
1097
     * @psalm-suppress InvalidReturnStatement
1098
     */
1099 28
    public static function encode(
1100
        string $to_encoding,
1101
        string $str,
1102
        bool $auto_detect_the_from_encoding = true,
1103
        string $from_encoding = ''
1104
    ): string {
1105 28
        if ($str === '' || $to_encoding === '') {
1106 13
            return $str;
1107
        }
1108
1109 28
        if ($to_encoding !== 'UTF-8' && $to_encoding !== 'CP850') {
1110 7
            $to_encoding = self::normalize_encoding($to_encoding, 'UTF-8');
1111
        }
1112
1113 28
        if ($from_encoding && $from_encoding !== 'UTF-8' && $from_encoding !== 'CP850') {
1114 2
            $from_encoding = self::normalize_encoding($from_encoding, null);
1115
        }
1116
1117
        if (
1118 28
            $to_encoding
1119
            &&
1120 28
            $from_encoding
1121
            &&
1122 28
            $from_encoding === $to_encoding
1123
        ) {
1124
            return $str;
1125
        }
1126
1127 28
        if ($to_encoding === 'JSON') {
1128 1
            $return = self::json_encode($str);
1129 1
            if ($return === false) {
1130
                throw new \InvalidArgumentException('The input string [' . $str . '] can not be used for json_encode().');
1131
            }
1132
1133 1
            return $return;
1134
        }
1135 28
        if ($from_encoding === 'JSON') {
1136 1
            $str = self::json_decode($str);
1137 1
            $from_encoding = '';
1138
        }
1139
1140 28
        if ($to_encoding === 'BASE64') {
1141 2
            return \base64_encode($str);
1142
        }
1143 28
        if ($from_encoding === 'BASE64') {
1144 2
            $str = \base64_decode($str, true);
1145 2
            $from_encoding = '';
1146
        }
1147
1148 28
        if ($to_encoding === 'HTML-ENTITIES') {
1149 2
            return self::html_encode($str, true, 'UTF-8');
1150
        }
1151 28
        if ($from_encoding === 'HTML-ENTITIES') {
1152 2
            $str = self::html_entity_decode($str, \ENT_COMPAT, 'UTF-8');
1153 2
            $from_encoding = '';
1154
        }
1155
1156 28
        $from_encoding_auto_detected = false;
1157
        if (
1158 28
            $auto_detect_the_from_encoding === true
1159
            ||
1160 28
            !$from_encoding
1161
        ) {
1162 28
            $from_encoding_auto_detected = self::str_detect_encoding($str);
1163
        }
1164
1165
        // DEBUG
1166
        //var_dump($to_encoding, $from_encoding, $from_encoding_auto_detected, $str, "\n\n");
1167
1168 28
        if ($from_encoding_auto_detected !== false) {
1169
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
1170 24
            $from_encoding = $from_encoding_auto_detected;
1171 7
        } elseif ($auto_detect_the_from_encoding === true) {
1172
            // fallback for the "autodetect"-mode
1173 7
            return self::to_utf8($str);
1174
        }
1175
1176
        if (
1177 24
            !$from_encoding
1178
            ||
1179 24
            $from_encoding === $to_encoding
1180
        ) {
1181 15
            return $str;
1182
        }
1183
1184
        if (
1185 19
            $to_encoding === 'UTF-8'
1186
            &&
1187
            (
1188 17
                $from_encoding === 'WINDOWS-1252'
1189
                ||
1190 19
                $from_encoding === 'ISO-8859-1'
1191
            )
1192
        ) {
1193 13
            return self::to_utf8($str);
1194
        }
1195
1196
        if (
1197 12
            $to_encoding === 'ISO-8859-1'
1198
            &&
1199
            (
1200 6
                $from_encoding === 'WINDOWS-1252'
1201
                ||
1202 12
                $from_encoding === 'UTF-8'
1203
            )
1204
        ) {
1205 6
            return self::to_iso8859($str);
1206
        }
1207
1208
        if (
1209 10
            $to_encoding !== 'UTF-8'
1210
            &&
1211 10
            $to_encoding !== 'ISO-8859-1'
1212
            &&
1213 10
            $to_encoding !== 'WINDOWS-1252'
1214
            &&
1215 10
            self::$SUPPORT['mbstring'] === false
1216
        ) {
1217
            \trigger_error('UTF8::encode() without mbstring cannot handle "' . $to_encoding . '" encoding', \E_USER_WARNING);
1218
        }
1219
1220 10
        if (self::$SUPPORT['mbstring'] === true) {
1221
            // warning: do not use the symfony polyfill here
1222 10
            $str_encoded = \mb_convert_encoding(
1223 10
                $str,
1224 10
                $to_encoding,
1225 10
                $from_encoding
1226
            );
1227
1228 10
            if ($str_encoded) {
1229 10
                return $str_encoded;
1230
            }
1231
        }
1232
1233
        $return = \iconv($from_encoding, $to_encoding, $str);
1234
        if ($return !== false) {
1235
            return $return;
1236
        }
1237
1238
        return $str;
1239
    }
1240
1241
    /**
1242
     * @param string $str
1243
     * @param string $from_charset      [optional] <p>Set the input charset.</p>
1244
     * @param string $to_charset        [optional] <p>Set the output charset.</p>
1245
     * @param string $transfer_encoding [optional] <p>Set the transfer encoding.</p>
1246
     * @param string $linefeed          [optional] <p>Set the used linefeed.</p>
1247
     * @param int    $indent            [optional] <p>Set the max length indent.</p>
1248
     *
1249
     * @return false|string
1250
     *                      <p>An encoded MIME field on success,
1251
     *                      or false if an error occurs during the encoding.</p>
1252
     */
1253
    public static function encode_mimeheader(
1254
        $str,
1255
        $from_charset = 'UTF-8',
1256
        $to_charset = 'UTF-8',
1257
        $transfer_encoding = 'Q',
1258
        $linefeed = '\\r\\n',
1259
        $indent = 76
1260
    ) {
1261
        if ($from_charset !== 'UTF-8' && $from_charset !== 'CP850') {
1262
            $from_charset = self::normalize_encoding($from_charset, 'UTF-8');
1263
        }
1264
1265
        if ($to_charset !== 'UTF-8' && $to_charset !== 'CP850') {
1266
            $to_charset = self::normalize_encoding($to_charset, 'UTF-8');
1267
        }
1268
1269
        return \iconv_mime_encode(
1270
            '',
1271
            $str,
1272
            [
1273
                'scheme'           => $transfer_encoding,
1274
                'line-length'      => $indent,
1275
                'input-charset'    => $from_charset,
1276
                'output-charset'   => $to_charset,
1277
                'line-break-chars' => $linefeed,
1278
            ]
1279
        );
1280
    }
1281
1282
    /**
1283
     * Create an extract from a sentence, so if the search-string was found, it try to centered in the output.
1284
     *
1285
     * @param string   $str                       <p>The input string.</p>
1286
     * @param string   $search                    <p>The searched string.</p>
1287
     * @param int|null $length                    [optional] <p>Default: null === text->length / 2</p>
1288
     * @param string   $replacer_for_skipped_text [optional] <p>Default: …</p>
1289
     * @param string   $encoding                  [optional] <p>Set the charset for e.g. "mb_" function</p>
1290
     *
1291
     * @return string
1292
     */
1293 1
    public static function extract_text(
1294
        string $str,
1295
        string $search = '',
1296
        int $length = null,
1297
        string $replacer_for_skipped_text = '…',
1298
        string $encoding = 'UTF-8'
1299
    ): string {
1300 1
        if ($str === '') {
1301 1
            return '';
1302
        }
1303
1304 1
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
1305
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
1306
        }
1307
1308 1
        $trim_chars = "\t\r\n -_()!~?=+/*\\,.:;\"'[]{}`&";
1309
1310 1
        if ($length === null) {
1311 1
            $length = (int) \round((int) self::strlen($str, $encoding) / 2, 0);
1312
        }
1313
1314 1
        if ($search === '') {
1315 1
            if ($encoding === 'UTF-8') {
1316 1
                if ($length > 0) {
1317 1
                    $string_length = (int) \mb_strlen($str);
1318 1
                    $end = ($length - 1) > $string_length ? $string_length : ($length - 1);
1319
                } else {
1320 1
                    $end = 0;
1321
                }
1322
1323 1
                $pos = (int) \min(
1324 1
                    \mb_strpos($str, ' ', $end),
1325 1
                    \mb_strpos($str, '.', $end)
1326
                );
1327
            } else {
1328
                if ($length > 0) {
1329
                    $string_length = (int) self::strlen($str, $encoding);
1330
                    $end = ($length - 1) > $string_length ? $string_length : ($length - 1);
1331
                } else {
1332
                    $end = 0;
1333
                }
1334
1335
                $pos = (int) \min(
1336
                    self::strpos($str, ' ', $end, $encoding),
1337
                    self::strpos($str, '.', $end, $encoding)
1338
                );
1339
            }
1340
1341 1
            if ($pos) {
1342 1
                if ($encoding === 'UTF-8') {
1343 1
                    $str_sub = \mb_substr($str, 0, $pos);
1344
                } else {
1345
                    $str_sub = self::substr($str, 0, $pos, $encoding);
1346
                }
1347
1348 1
                if ($str_sub === false) {
1349
                    return '';
1350
                }
1351
1352 1
                return \rtrim($str_sub, $trim_chars) . $replacer_for_skipped_text;
1353
            }
1354
1355
            return $str;
1356
        }
1357
1358 1
        if ($encoding === 'UTF-8') {
1359 1
            $word_position = (int) \mb_stripos($str, $search);
1360 1
            $half_side = (int) ($word_position - $length / 2 + (int) \mb_strlen($search) / 2);
1361
        } else {
1362
            $word_position = (int) self::stripos($str, $search, 0, $encoding);
1363
            $half_side = (int) ($word_position - $length / 2 + (int) self::strlen($search, $encoding) / 2);
1364
        }
1365
1366 1
        $pos_start = 0;
1367 1
        if ($half_side > 0) {
1368 1
            if ($encoding === 'UTF-8') {
1369 1
                $half_text = \mb_substr($str, 0, $half_side);
1370
            } else {
1371
                $half_text = self::substr($str, 0, $half_side, $encoding);
1372
            }
1373 1
            if ($half_text !== false) {
1374 1
                if ($encoding === 'UTF-8') {
1375 1
                    $pos_start = (int) \max(
1376 1
                        \mb_strrpos($half_text, ' '),
1377 1
                        \mb_strrpos($half_text, '.')
1378
                    );
1379
                } else {
1380
                    $pos_start = (int) \max(
1381
                        self::strrpos($half_text, ' ', 0, $encoding),
1382
                        self::strrpos($half_text, '.', 0, $encoding)
1383
                    );
1384
                }
1385
            }
1386
        }
1387
1388 1
        if ($word_position && $half_side > 0) {
1389 1
            $offset = $pos_start + $length - 1;
1390 1
            $real_length = (int) self::strlen($str, $encoding);
1391
1392 1
            if ($offset > $real_length) {
1393
                $offset = $real_length;
1394
            }
1395
1396 1
            if ($encoding === 'UTF-8') {
1397 1
                $pos_end = (int) \min(
1398 1
                    \mb_strpos($str, ' ', $offset),
1399 1
                    \mb_strpos($str, '.', $offset)
1400 1
                ) - $pos_start;
1401
            } else {
1402
                $pos_end = (int) \min(
1403
                    self::strpos($str, ' ', $offset, $encoding),
1404
                    self::strpos($str, '.', $offset, $encoding)
1405
                ) - $pos_start;
1406
            }
1407
1408 1
            if (!$pos_end || $pos_end <= 0) {
1409 1
                if ($encoding === 'UTF-8') {
1410 1
                    $str_sub = \mb_substr($str, $pos_start, (int) \mb_strlen($str));
1411
                } else {
1412
                    $str_sub = self::substr($str, $pos_start, (int) self::strlen($str, $encoding), $encoding);
1413
                }
1414 1
                if ($str_sub !== false) {
1415 1
                    $extract = $replacer_for_skipped_text . \ltrim($str_sub, $trim_chars);
1416
                } else {
1417 1
                    $extract = '';
1418
                }
1419
            } else {
1420 1
                if ($encoding === 'UTF-8') {
1421 1
                    $str_sub = \mb_substr($str, $pos_start, $pos_end);
1422
                } else {
1423
                    $str_sub = self::substr($str, $pos_start, $pos_end, $encoding);
1424
                }
1425 1
                if ($str_sub !== false) {
1426 1
                    $extract = $replacer_for_skipped_text . \trim($str_sub, $trim_chars) . $replacer_for_skipped_text;
1427
                } else {
1428 1
                    $extract = '';
1429
                }
1430
            }
1431
        } else {
1432 1
            $offset = $length - 1;
1433 1
            $true_length = (int) self::strlen($str, $encoding);
1434
1435 1
            if ($offset > $true_length) {
1436
                $offset = $true_length;
1437
            }
1438
1439 1
            if ($encoding === 'UTF-8') {
1440 1
                $pos_end = (int) \min(
1441 1
                    \mb_strpos($str, ' ', $offset),
1442 1
                    \mb_strpos($str, '.', $offset)
1443
                );
1444
            } else {
1445
                $pos_end = (int) \min(
1446
                    self::strpos($str, ' ', $offset, $encoding),
1447
                    self::strpos($str, '.', $offset, $encoding)
1448
                );
1449
            }
1450
1451 1
            if ($pos_end) {
1452 1
                if ($encoding === 'UTF-8') {
1453 1
                    $str_sub = \mb_substr($str, 0, $pos_end);
1454
                } else {
1455
                    $str_sub = self::substr($str, 0, $pos_end, $encoding);
1456
                }
1457 1
                if ($str_sub !== false) {
1458 1
                    $extract = \rtrim($str_sub, $trim_chars) . $replacer_for_skipped_text;
1459
                } else {
1460 1
                    $extract = '';
1461
                }
1462
            } else {
1463 1
                $extract = $str;
1464
            }
1465
        }
1466
1467 1
        return $extract;
1468
    }
1469
1470
    /**
1471
     * Reads entire file into a string.
1472
     *
1473
     * WARNING: Do not use UTF-8 Option ($convert_to_utf8) for binary files (e.g.: images) !!!
1474
     *
1475
     * @see http://php.net/manual/en/function.file-get-contents.php
1476
     *
1477
     * @param string        $filename         <p>
1478
     *                                        Name of the file to read.
1479
     *                                        </p>
1480
     * @param bool          $use_include_path [optional] <p>
1481
     *                                        Prior to PHP 5, this parameter is called
1482
     *                                        use_include_path and is a bool.
1483
     *                                        As of PHP 5 the FILE_USE_INCLUDE_PATH can be used
1484
     *                                        to trigger include path
1485
     *                                        search.
1486
     *                                        </p>
1487
     * @param resource|null $context          [optional] <p>
1488
     *                                        A valid context resource created with
1489
     *                                        stream_context_create. If you don't need to use a
1490
     *                                        custom context, you can skip this parameter by &null;.
1491
     *                                        </p>
1492
     * @param int|null      $offset           [optional] <p>
1493
     *                                        The offset where the reading starts.
1494
     *                                        </p>
1495
     * @param int|null      $max_length       [optional] <p>
1496
     *                                        Maximum length of data read. The default is to read until end
1497
     *                                        of file is reached.
1498
     *                                        </p>
1499
     * @param int           $timeout          <p>The time in seconds for the timeout.</p>
1500
     * @param bool          $convert_to_utf8  <strong>WARNING!!!</strong> <p>Maybe you can't use this option for
1501
     *                                        some files, because they used non default utf-8 chars. Binary files
1502
     *                                        like images or pdf will not be converted.</p>
1503
     * @param string        $from_encoding    [optional] <p>e.g. 'UTF-16', 'UTF-8', 'ISO-8859-1', etc.<br>
1504
     *                                        A empty string will trigger the autodetect anyway.</p>
1505
     *
1506
     * @return false|string
1507
     *                      <p>The function returns the read data as string or <b>false</b> on failure.</p>
1508
     */
1509 12
    public static function file_get_contents(
1510
        string $filename,
1511
        bool $use_include_path = false,
1512
        $context = null,
1513
        int $offset = null,
1514
        int $max_length = null,
1515
        int $timeout = 10,
1516
        bool $convert_to_utf8 = true,
1517
        string $from_encoding = ''
1518
    ) {
1519
        // init
1520 12
        $filename = \filter_var($filename, \FILTER_SANITIZE_STRING);
1521
        /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
1522 12
        if ($filename === false) {
1523
            return false;
1524
        }
1525
1526 12
        if ($timeout && $context === null) {
1527 9
            $context = \stream_context_create(
1528
                [
1529
                    'http' => [
1530 9
                        'timeout' => $timeout,
1531
                    ],
1532
                ]
1533
            );
1534
        }
1535
1536 12
        if ($offset === null) {
1537 12
            $offset = 0;
1538
        }
1539
1540 12
        if (\is_int($max_length) === true) {
1541 2
            $data = \file_get_contents($filename, $use_include_path, $context, $offset, $max_length);
1542
        } else {
1543 12
            $data = \file_get_contents($filename, $use_include_path, $context, $offset);
1544
        }
1545
1546
        // return false on error
1547 12
        if ($data === false) {
1548
            return false;
1549
        }
1550
1551 12
        if ($convert_to_utf8 === true) {
1552
            if (
1553 12
                self::is_binary($data, true) !== true
1554
                ||
1555 9
                self::is_utf16($data, false) !== false
1556
                ||
1557 12
                self::is_utf32($data, false) !== false
1558
            ) {
1559 9
                $data = self::encode('UTF-8', $data, false, $from_encoding);
1560 9
                $data = self::cleanup($data);
1561
            }
1562
        }
1563
1564 12
        return $data;
1565
    }
1566
1567
    /**
1568
     * Checks if a file starts with BOM (Byte Order Mark) character.
1569
     *
1570
     * @param string $file_path <p>Path to a valid file.</p>
1571
     *
1572
     * @throws \RuntimeException if file_get_contents() returned false
1573
     *
1574
     * @return bool
1575
     *              <p><strong>true</strong> if the file has BOM at the start, <strong>false</strong> otherwise</p>
1576
     */
1577 2
    public static function file_has_bom(string $file_path): bool
1578
    {
1579 2
        $file_content = \file_get_contents($file_path);
1580 2
        if ($file_content === false) {
1581
            throw new \RuntimeException('file_get_contents() returned false for:' . $file_path);
1582
        }
1583
1584 2
        return self::string_has_bom($file_content);
1585
    }
1586
1587
    /**
1588
     * Normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1589
     *
1590
     * @param mixed  $var
1591
     * @param int    $normalization_form
1592
     * @param string $leading_combining
1593
     *
1594
     * @return mixed
1595
     */
1596 62
    public static function filter(
1597
        $var,
1598
        int $normalization_form = \Normalizer::NFC,
1599
        string $leading_combining = '◌'
1600
    ) {
1601 62
        switch (\gettype($var)) {
1602 62
            case 'array':
1603
                /** @noinspection ForeachSourceInspection */
1604 6
                foreach ($var as $k => &$v) {
1605 6
                    $v = self::filter($v, $normalization_form, $leading_combining);
1606
                }
1607 6
                unset($v);
1608
1609 6
                break;
1610 62
            case 'object':
1611
                /** @noinspection ForeachSourceInspection */
1612 4
                foreach ($var as $k => &$v) {
1613 4
                    $v = self::filter($v, $normalization_form, $leading_combining);
1614
                }
1615 4
                unset($v);
1616
1617 4
                break;
1618 62
            case 'string':
1619
1620 62
                if (\strpos($var, "\r") !== false) {
1621
                    // Workaround https://bugs.php.net/65732
1622 3
                    $var = self::normalize_line_ending($var);
1623
                }
1624
1625 62
                if (ASCII::is_ascii($var) === false) {
1626 32
                    if (\Normalizer::isNormalized($var, $normalization_form)) {
1627 27
                        $n = '-';
1628
                    } else {
1629 12
                        $n = \Normalizer::normalize($var, $normalization_form);
1630
1631 12
                        if (isset($n[0])) {
1632 7
                            $var = $n;
1633
                        } else {
1634 8
                            $var = self::encode('UTF-8', $var, true);
1635
                        }
1636
                    }
1637
1638
                    if (
1639 32
                        $var[0] >= "\x80"
1640
                        &&
1641 32
                        isset($n[0], $leading_combining[0])
1642
                        &&
1643 32
                        \preg_match('/^\\p{Mn}/u', $var)
1644
                    ) {
1645
                        // Prevent leading combining chars
1646
                        // for NFC-safe concatenations.
1647 3
                        $var = $leading_combining . $var;
1648
                    }
1649
                }
1650
1651 62
                break;
1652
        }
1653
1654 62
        return $var;
1655
    }
1656
1657
    /**
1658
     * "filter_input()"-wrapper with normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1659
     *
1660
     * Gets a specific external variable by name and optionally filters it
1661
     *
1662
     * @see http://php.net/manual/en/function.filter-input.php
1663
     *
1664
     * @param int    $type          <p>
1665
     *                              One of <b>INPUT_GET</b>, <b>INPUT_POST</b>,
1666
     *                              <b>INPUT_COOKIE</b>, <b>INPUT_SERVER</b>, or
1667
     *                              <b>INPUT_ENV</b>.
1668
     *                              </p>
1669
     * @param string $variable_name <p>
1670
     *                              Name of a variable to get.
1671
     *                              </p>
1672
     * @param int    $filter        [optional] <p>
1673
     *                              The ID of the filter to apply. The
1674
     *                              manual page lists the available filters.
1675
     *                              </p>
1676
     * @param mixed  $options       [optional] <p>
1677
     *                              Associative array of options or bitwise disjunction of flags. If filter
1678
     *                              accepts options, flags can be provided in "flags" field of array.
1679
     *                              </p>
1680
     *
1681
     * @return mixed Value of the requested variable on success, <b>FALSE</b> if the filter fails, or <b>NULL</b> if the
1682
     *               <i>variable_name</i> variable is not set. If the flag <b>FILTER_NULL_ON_FAILURE</b> is used, it
1683
     *               returns <b>FALSE</b> if the variable is not set and <b>NULL</b> if the filter fails.
1684
     */
1685
    public static function filter_input(
1686
        int $type,
1687
        string $variable_name,
1688
        int $filter = \FILTER_DEFAULT,
1689
        $options = null
1690
    ) {
1691
        if (\func_num_args() < 4) {
1692
            $var = \filter_input($type, $variable_name, $filter);
1693
        } else {
1694
            $var = \filter_input($type, $variable_name, $filter, $options);
1695
        }
1696
1697
        return self::filter($var);
1698
    }
1699
1700
    /**
1701
     * "filter_input_array()"-wrapper with normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1702
     *
1703
     * Gets external variables and optionally filters them
1704
     *
1705
     * @see http://php.net/manual/en/function.filter-input-array.php
1706
     *
1707
     * @param int   $type       <p>
1708
     *                          One of <b>INPUT_GET</b>, <b>INPUT_POST</b>,
1709
     *                          <b>INPUT_COOKIE</b>, <b>INPUT_SERVER</b>, or
1710
     *                          <b>INPUT_ENV</b>.
1711
     *                          </p>
1712
     * @param mixed $definition [optional] <p>
1713
     *                          An array defining the arguments. A valid key is a string
1714
     *                          containing a variable name and a valid value is either a filter type, or an array
1715
     *                          optionally specifying the filter, flags and options. If the value is an
1716
     *                          array, valid keys are filter which specifies the
1717
     *                          filter type,
1718
     *                          flags which specifies any flags that apply to the
1719
     *                          filter, and options which specifies any options that
1720
     *                          apply to the filter. See the example below for a better understanding.
1721
     *                          </p>
1722
     *                          <p>
1723
     *                          This parameter can be also an integer holding a filter constant. Then all values in the
1724
     *                          input array are filtered by this filter.
1725
     *                          </p>
1726
     * @param bool  $add_empty  [optional] <p>
1727
     *                          Add missing keys as <b>NULL</b> to the return value.
1728
     *                          </p>
1729
     *
1730
     * @return mixed An array containing the values of the requested variables on success, or <b>FALSE</b> on failure.
1731
     *               An array value will be <b>FALSE</b> if the filter fails, or <b>NULL</b> if the variable is not
1732
     *               set. Or if the flag <b>FILTER_NULL_ON_FAILURE</b> is used, it returns <b>FALSE</b> if the variable
1733
     *               is not set and <b>NULL</b> if the filter fails.
1734
     */
1735
    public static function filter_input_array(
1736
        int $type,
1737
        $definition = null,
1738
        bool $add_empty = true
1739
    ) {
1740
        if (\func_num_args() < 2) {
1741
            $a = \filter_input_array($type);
1742
        } else {
1743
            $a = \filter_input_array($type, $definition, $add_empty);
1744
        }
1745
1746
        return self::filter($a);
1747
    }
1748
1749
    /**
1750
     * "filter_var()"-wrapper with normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1751
     *
1752
     * Filters a variable with a specified filter
1753
     *
1754
     * @see http://php.net/manual/en/function.filter-var.php
1755
     *
1756
     * @param mixed $variable <p>
1757
     *                        Value to filter.
1758
     *                        </p>
1759
     * @param int   $filter   [optional] <p>
1760
     *                        The ID of the filter to apply. The
1761
     *                        manual page lists the available filters.
1762
     *                        </p>
1763
     * @param mixed $options  [optional] <p>
1764
     *                        Associative array of options or bitwise disjunction of flags. If filter
1765
     *                        accepts options, flags can be provided in "flags" field of array. For
1766
     *                        the "callback" filter, callable type should be passed. The
1767
     *                        callback must accept one argument, the value to be filtered, and return
1768
     *                        the value after filtering/sanitizing it.
1769
     *                        </p>
1770
     *                        <p>
1771
     *                        <code>
1772
     *                        // for filters that accept options, use this format
1773
     *                        $options = array(
1774
     *                        'options' => array(
1775
     *                        'default' => 3, // value to return if the filter fails
1776
     *                        // other options here
1777
     *                        'min_range' => 0
1778
     *                        ),
1779
     *                        'flags' => FILTER_FLAG_ALLOW_OCTAL,
1780
     *                        );
1781
     *                        $var = filter_var('0755', FILTER_VALIDATE_INT, $options);
1782
     *                        // for filter that only accept flags, you can pass them directly
1783
     *                        $var = filter_var('oops', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
1784
     *                        // for filter that only accept flags, you can also pass as an array
1785
     *                        $var = filter_var('oops', FILTER_VALIDATE_BOOLEAN,
1786
     *                        array('flags' => FILTER_NULL_ON_FAILURE));
1787
     *                        // callback validate filter
1788
     *                        function foo($value)
1789
     *                        {
1790
     *                        // Expected format: Surname, GivenNames
1791
     *                        if (strpos($value, ", ") === false) return false;
1792
     *                        list($surname, $givennames) = explode(", ", $value, 2);
1793
     *                        $empty = (empty($surname) || empty($givennames));
1794
     *                        $notstrings = (!is_string($surname) || !is_string($givennames));
1795
     *                        if ($empty || $notstrings) {
1796
     *                        return false;
1797
     *                        } else {
1798
     *                        return $value;
1799
     *                        }
1800
     *                        }
1801
     *                        $var = filter_var('Doe, Jane Sue', FILTER_CALLBACK, array('options' => 'foo'));
1802
     *                        </code>
1803
     *                        </p>
1804
     *
1805
     * @return mixed the filtered data, or <b>FALSE</b> if the filter fails
1806
     */
1807 2
    public static function filter_var(
1808
        $variable,
1809
        int $filter = \FILTER_DEFAULT,
1810
        $options = null
1811
    ) {
1812 2
        if (\func_num_args() < 3) {
1813 2
            $variable = \filter_var($variable, $filter);
1814
        } else {
1815 2
            $variable = \filter_var($variable, $filter, $options);
1816
        }
1817
1818 2
        return self::filter($variable);
1819
    }
1820
1821
    /**
1822
     * "filter_var_array()"-wrapper with normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1823
     *
1824
     * Gets multiple variables and optionally filters them
1825
     *
1826
     * @see http://php.net/manual/en/function.filter-var-array.php
1827
     *
1828
     * @param array $data       <p>
1829
     *                          An array with string keys containing the data to filter.
1830
     *                          </p>
1831
     * @param mixed $definition [optional] <p>
1832
     *                          An array defining the arguments. A valid key is a string
1833
     *                          containing a variable name and a valid value is either a
1834
     *                          filter type, or an
1835
     *                          array optionally specifying the filter, flags and options.
1836
     *                          If the value is an array, valid keys are filter
1837
     *                          which specifies the filter type,
1838
     *                          flags which specifies any flags that apply to the
1839
     *                          filter, and options which specifies any options that
1840
     *                          apply to the filter. See the example below for a better understanding.
1841
     *                          </p>
1842
     *                          <p>
1843
     *                          This parameter can be also an integer holding a filter constant. Then all values in the
1844
     *                          input array are filtered by this filter.
1845
     *                          </p>
1846
     * @param bool  $add_empty  [optional] <p>
1847
     *                          Add missing keys as <b>NULL</b> to the return value.
1848
     *                          </p>
1849
     *
1850
     * @return mixed an array containing the values of the requested variables on success, or <b>FALSE</b> on failure.
1851
     *               An array value will be <b>FALSE</b> if the filter fails, or <b>NULL</b> if the variable is not
1852
     *               set
1853
     */
1854 2
    public static function filter_var_array(
1855
        array $data,
1856
        $definition = null,
1857
        bool $add_empty = true
1858
    ) {
1859 2
        if (\func_num_args() < 2) {
1860 2
            $a = \filter_var_array($data);
1861
        } else {
1862 2
            $a = \filter_var_array($data, $definition, $add_empty);
1863
        }
1864
1865 2
        return self::filter($a);
1866
    }
1867
1868
    /**
1869
     * Checks whether finfo is available on the server.
1870
     *
1871
     * @return bool
1872
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
1873
     */
1874
    public static function finfo_loaded(): bool
1875
    {
1876
        return \class_exists('finfo');
1877
    }
1878
1879
    /**
1880
     * Returns the first $n characters of the string.
1881
     *
1882
     * @param string $str      <p>The input string.</p>
1883
     * @param int    $n        <p>Number of characters to retrieve from the start.</p>
1884
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
1885
     *
1886
     * @return string
1887
     */
1888 13
    public static function first_char(
1889
        string $str,
1890
        int $n = 1,
1891
        string $encoding = 'UTF-8'
1892
    ): string {
1893 13
        if ($str === '' || $n <= 0) {
1894 5
            return '';
1895
        }
1896
1897 8
        if ($encoding === 'UTF-8') {
1898 4
            return (string) \mb_substr($str, 0, $n);
1899
        }
1900
1901 4
        return (string) self::substr($str, 0, $n, $encoding);
1902
    }
1903
1904
    /**
1905
     * Check if the number of Unicode characters isn't greater than the specified integer.
1906
     *
1907
     * @param string $str      the original string to be checked
1908
     * @param int    $box_size the size in number of chars to be checked against string
1909
     *
1910
     * @return bool true if string is less than or equal to $box_size, false otherwise
1911
     */
1912 2
    public static function fits_inside(string $str, int $box_size): bool
1913
    {
1914 2
        return (int) self::strlen($str) <= $box_size;
1915
    }
1916
1917
    /**
1918
     * Try to fix simple broken UTF-8 strings.
1919
     *
1920
     * INFO: Take a look at "UTF8::fix_utf8()" if you need a more advanced fix for broken UTF-8 strings.
1921
     *
1922
     * If you received an UTF-8 string that was converted from Windows-1252 as it was ISO-8859-1
1923
     * (ignoring Windows-1252 chars from 80 to 9F) use this function to fix it.
1924
     * See: http://en.wikipedia.org/wiki/Windows-1252
1925
     *
1926
     * @param string $str <p>The input string</p>
1927
     *
1928
     * @return string
1929
     */
1930 46
    public static function fix_simple_utf8(string $str): string
1931
    {
1932 46
        if ($str === '') {
1933 4
            return '';
1934
        }
1935
1936 46
        static $BROKEN_UTF8_TO_UTF8_KEYS_CACHE = null;
1937 46
        static $BROKEN_UTF8_TO_UTF8_VALUES_CACHE = null;
1938
1939 46
        if ($BROKEN_UTF8_TO_UTF8_KEYS_CACHE === null) {
1940 1
            if (self::$BROKEN_UTF8_FIX === null) {
1941 1
                self::$BROKEN_UTF8_FIX = self::getData('utf8_fix');
1942
            }
1943
1944 1
            $BROKEN_UTF8_TO_UTF8_KEYS_CACHE = \array_keys(self::$BROKEN_UTF8_FIX);
1945 1
            $BROKEN_UTF8_TO_UTF8_VALUES_CACHE = \array_values(self::$BROKEN_UTF8_FIX);
1946
        }
1947
1948 46
        return \str_replace($BROKEN_UTF8_TO_UTF8_KEYS_CACHE, $BROKEN_UTF8_TO_UTF8_VALUES_CACHE, $str);
1949
    }
1950
1951
    /**
1952
     * Fix a double (or multiple) encoded UTF8 string.
1953
     *
1954
     * @param string|string[] $str you can use a string or an array of strings
1955
     *
1956
     * @return string|string[]
1957
     *                         Will return the fixed input-"array" or
1958
     *                         the fixed input-"string"
1959
     *
1960
     * @psalm-suppress InvalidReturnType
1961
     */
1962 2
    public static function fix_utf8($str)
1963
    {
1964 2
        if (\is_array($str) === true) {
1965 2
            foreach ($str as $k => &$v) {
1966 2
                $v = self::fix_utf8($v);
1967
            }
1968 2
            unset($v);
1969
1970
            /**
1971
             * @psalm-suppress InvalidReturnStatement
1972
             */
1973 2
            return $str;
1974
        }
1975
1976 2
        $str = (string) $str;
1977 2
        $last = '';
1978 2
        while ($last !== $str) {
1979 2
            $last = $str;
1980
            /**
1981
             * @psalm-suppress PossiblyInvalidArgument
1982
             */
1983 2
            $str = self::to_utf8(
1984 2
                self::utf8_decode($str, true)
1985
            );
1986
        }
1987
1988
        /**
1989
         * @psalm-suppress InvalidReturnStatement
1990
         */
1991 2
        return $str;
1992
    }
1993
1994
    /**
1995
     * Get character of a specific character.
1996
     *
1997
     * @param string $char
1998
     *
1999
     * @return string 'RTL' or 'LTR'
2000
     */
2001 2
    public static function getCharDirection(string $char): string
2002
    {
2003 2
        if (self::$SUPPORT['intlChar'] === true) {
2004
            /** @noinspection PhpComposerExtensionStubsInspection */
2005 2
            $tmp_return = \IntlChar::charDirection($char);
2006
2007
            // from "IntlChar"-Class
2008
            $char_direction = [
2009 2
                'RTL' => [1, 13, 14, 15, 21],
2010
                'LTR' => [0, 11, 12, 20],
2011
            ];
2012
2013 2
            if (\in_array($tmp_return, $char_direction['LTR'], true)) {
2014
                return 'LTR';
2015
            }
2016
2017 2
            if (\in_array($tmp_return, $char_direction['RTL'], true)) {
2018 2
                return 'RTL';
2019
            }
2020
        }
2021
2022 2
        $c = static::chr_to_decimal($char);
2023
2024 2
        if (!($c >= 0x5be && $c <= 0x10b7f)) {
2025 2
            return 'LTR';
2026
        }
2027
2028 2
        if ($c <= 0x85e) {
2029 2
            if ($c === 0x5be ||
2030 2
                $c === 0x5c0 ||
2031 2
                $c === 0x5c3 ||
2032 2
                $c === 0x5c6 ||
2033 2
                ($c >= 0x5d0 && $c <= 0x5ea) ||
2034 2
                ($c >= 0x5f0 && $c <= 0x5f4) ||
2035 2
                $c === 0x608 ||
2036 2
                $c === 0x60b ||
2037 2
                $c === 0x60d ||
2038 2
                $c === 0x61b ||
2039 2
                ($c >= 0x61e && $c <= 0x64a) ||
2040
                ($c >= 0x66d && $c <= 0x66f) ||
2041
                ($c >= 0x671 && $c <= 0x6d5) ||
2042
                ($c >= 0x6e5 && $c <= 0x6e6) ||
2043
                ($c >= 0x6ee && $c <= 0x6ef) ||
2044
                ($c >= 0x6fa && $c <= 0x70d) ||
2045
                $c === 0x710 ||
2046
                ($c >= 0x712 && $c <= 0x72f) ||
2047
                ($c >= 0x74d && $c <= 0x7a5) ||
2048
                $c === 0x7b1 ||
2049
                ($c >= 0x7c0 && $c <= 0x7ea) ||
2050
                ($c >= 0x7f4 && $c <= 0x7f5) ||
2051
                $c === 0x7fa ||
2052
                ($c >= 0x800 && $c <= 0x815) ||
2053
                $c === 0x81a ||
2054
                $c === 0x824 ||
2055
                $c === 0x828 ||
2056
                ($c >= 0x830 && $c <= 0x83e) ||
2057
                ($c >= 0x840 && $c <= 0x858) ||
2058 2
                $c === 0x85e
2059
            ) {
2060 2
                return 'RTL';
2061
            }
2062 2
        } elseif ($c === 0x200f) {
2063
            return 'RTL';
2064 2
        } elseif ($c >= 0xfb1d) {
2065 2
            if ($c === 0xfb1d ||
2066 2
                ($c >= 0xfb1f && $c <= 0xfb28) ||
2067 2
                ($c >= 0xfb2a && $c <= 0xfb36) ||
2068 2
                ($c >= 0xfb38 && $c <= 0xfb3c) ||
2069 2
                $c === 0xfb3e ||
2070 2
                ($c >= 0xfb40 && $c <= 0xfb41) ||
2071 2
                ($c >= 0xfb43 && $c <= 0xfb44) ||
2072 2
                ($c >= 0xfb46 && $c <= 0xfbc1) ||
2073 2
                ($c >= 0xfbd3 && $c <= 0xfd3d) ||
2074 2
                ($c >= 0xfd50 && $c <= 0xfd8f) ||
2075 2
                ($c >= 0xfd92 && $c <= 0xfdc7) ||
2076 2
                ($c >= 0xfdf0 && $c <= 0xfdfc) ||
2077 2
                ($c >= 0xfe70 && $c <= 0xfe74) ||
2078 2
                ($c >= 0xfe76 && $c <= 0xfefc) ||
2079 2
                ($c >= 0x10800 && $c <= 0x10805) ||
2080 2
                $c === 0x10808 ||
2081 2
                ($c >= 0x1080a && $c <= 0x10835) ||
2082 2
                ($c >= 0x10837 && $c <= 0x10838) ||
2083 2
                $c === 0x1083c ||
2084 2
                ($c >= 0x1083f && $c <= 0x10855) ||
2085 2
                ($c >= 0x10857 && $c <= 0x1085f) ||
2086 2
                ($c >= 0x10900 && $c <= 0x1091b) ||
2087 2
                ($c >= 0x10920 && $c <= 0x10939) ||
2088 2
                $c === 0x1093f ||
2089 2
                $c === 0x10a00 ||
2090 2
                ($c >= 0x10a10 && $c <= 0x10a13) ||
2091 2
                ($c >= 0x10a15 && $c <= 0x10a17) ||
2092 2
                ($c >= 0x10a19 && $c <= 0x10a33) ||
2093 2
                ($c >= 0x10a40 && $c <= 0x10a47) ||
2094 2
                ($c >= 0x10a50 && $c <= 0x10a58) ||
2095 2
                ($c >= 0x10a60 && $c <= 0x10a7f) ||
2096 2
                ($c >= 0x10b00 && $c <= 0x10b35) ||
2097 2
                ($c >= 0x10b40 && $c <= 0x10b55) ||
2098 2
                ($c >= 0x10b58 && $c <= 0x10b72) ||
2099 2
                ($c >= 0x10b78 && $c <= 0x10b7f)
2100
            ) {
2101 2
                return 'RTL';
2102
            }
2103
        }
2104
2105 2
        return 'LTR';
2106
    }
2107
2108
    /**
2109
     * Check for php-support.
2110
     *
2111
     * @param string|null $key
2112
     *
2113
     * @return mixed
2114
     *               Return the full support-"array", if $key === null<br>
2115
     *               return bool-value, if $key is used and available<br>
2116
     *               otherwise return <strong>null</strong>
2117
     */
2118 27
    public static function getSupportInfo(string $key = null)
2119
    {
2120 27
        if ($key === null) {
2121 4
            return self::$SUPPORT;
2122
        }
2123
2124 25
        if (self::$INTL_TRANSLITERATOR_LIST === null) {
2125 1
            self::$INTL_TRANSLITERATOR_LIST = self::getData('transliterator_list');
2126
        }
2127
        // compatibility fix for old versions
2128 25
        self::$SUPPORT['intl__transliterator_list_ids'] = self::$INTL_TRANSLITERATOR_LIST;
2129
2130 25
        return self::$SUPPORT[$key] ?? null;
2131
    }
2132
2133
    /**
2134
     * Warning: this method only works for some file-types (png, jpg)
2135
     *          if you need more supported types, please use e.g. "finfo"
2136
     *
2137
     * @param string $str
2138
     * @param array  $fallback with this keys: 'ext', 'mime', 'type'
2139
     *
2140
     * @return array
2141
     *               with this keys: 'ext', 'mime', 'type'
2142
     */
2143 39
    public static function get_file_type(
2144
        string $str,
2145
        array $fallback = [
2146
            'ext'  => null,
2147
            'mime' => 'application/octet-stream',
2148
            'type' => null,
2149
        ]
2150
    ): array {
2151 39
        if ($str === '') {
2152
            return $fallback;
2153
        }
2154
2155
        /** @var false|string $str_info - needed for PhpStan (stubs error) */
2156 39
        $str_info = \substr($str, 0, 2);
2157 39
        if ($str_info === false || \strlen($str_info) !== 2) {
2158 11
            return $fallback;
2159
        }
2160
2161
        // DEBUG
2162
        //var_dump($str_info);
2163
2164
        /** @var array|false $str_info - needed for PhpStan (stubs error) */
2165 35
        $str_info = \unpack('C2chars', $str_info);
0 ignored issues
show
Bug introduced by
$str_info of type array|false is incompatible with the type string expected by parameter $data of unpack(). ( Ignorable by Annotation )

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

2165
        $str_info = \unpack('C2chars', /** @scrutinizer ignore-type */ $str_info);
Loading history...
2166 35
        if ($str_info === false) {
2167
            return $fallback;
2168
        }
2169
        /** @noinspection OffsetOperationsInspection */
2170 35
        $type_code = (int) ($str_info['chars1'] . $str_info['chars2']);
2171
2172
        // DEBUG
2173
        //var_dump($type_code);
2174
2175
        //
2176
        // info: https://en.wikipedia.org/wiki/Magic_number_%28programming%29#Format_indicator
2177
        //
2178
        switch ($type_code) {
2179
            // WARNING: do not add too simple comparisons, because of false-positive results:
2180
            //
2181
            // 3780 => 'pdf', 7790 => 'exe', 7784 => 'midi', 8075 => 'zip',
2182
            // 8297 => 'rar', 7173 => 'gif', 7373 => 'tiff' 6677 => 'bmp', ...
2183
            //
2184 35
            case 255216:
2185
                $ext = 'jpg';
2186
                $mime = 'image/jpeg';
2187
                $type = 'binary';
2188
2189
                break;
2190 35
            case 13780:
2191 7
                $ext = 'png';
2192 7
                $mime = 'image/png';
2193 7
                $type = 'binary';
2194
2195 7
                break;
2196
            default:
2197 34
                return $fallback;
2198
        }
2199
2200
        return [
2201 7
            'ext'  => $ext,
2202 7
            'mime' => $mime,
2203 7
            'type' => $type,
2204
        ];
2205
    }
2206
2207
    /**
2208
     * @param int    $length         <p>Length of the random string.</p>
2209
     * @param string $possible_chars [optional] <p>Characters string for the random selection.</p>
2210
     * @param string $encoding       [optional] <p>Set the charset for e.g. "mb_" function</p>
2211
     *
2212
     * @return string
2213
     */
2214 1
    public static function get_random_string(
2215
        int $length,
2216
        string $possible_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
2217
        string $encoding = 'UTF-8'
2218
    ): string {
2219
        // init
2220 1
        $i = 0;
2221 1
        $str = '';
2222
2223
        //
2224
        // add random chars
2225
        //
2226
2227 1
        if ($encoding === 'UTF-8') {
2228 1
            $max_length = (int) \mb_strlen($possible_chars);
2229 1
            if ($max_length === 0) {
2230 1
                return '';
2231
            }
2232
2233 1
            while ($i < $length) {
2234
                try {
2235 1
                    $rand_int = \random_int(0, $max_length - 1);
2236
                } catch (\Exception $e) {
2237
                    /** @noinspection RandomApiMigrationInspection */
2238
                    $rand_int = \mt_rand(0, $max_length - 1);
2239
                }
2240 1
                $char = \mb_substr($possible_chars, $rand_int, 1);
2241 1
                if ($char !== false) {
2242 1
                    $str .= $char;
2243 1
                    ++$i;
2244
                }
2245
            }
2246
        } else {
2247
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
2248
2249
            $max_length = (int) self::strlen($possible_chars, $encoding);
2250
            if ($max_length === 0) {
2251
                return '';
2252
            }
2253
2254
            while ($i < $length) {
2255
                try {
2256
                    $rand_int = \random_int(0, $max_length - 1);
2257
                } catch (\Exception $e) {
2258
                    /** @noinspection RandomApiMigrationInspection */
2259
                    $rand_int = \mt_rand(0, $max_length - 1);
2260
                }
2261
                $char = self::substr($possible_chars, $rand_int, 1, $encoding);
2262
                if ($char !== false) {
2263
                    $str .= $char;
2264
                    ++$i;
2265
                }
2266
            }
2267
        }
2268
2269 1
        return $str;
2270
    }
2271
2272
    /**
2273
     * @param int|string $entropy_extra [optional] <p>Extra entropy via a string or int value.</p>
2274
     * @param bool       $use_md5       [optional] <p>Return the unique identifier as md5-hash? Default: true</p>
2275
     *
2276
     * @return string
2277
     */
2278 1
    public static function get_unique_string($entropy_extra = '', bool $use_md5 = true): string
2279
    {
2280 1
        $unique_helper = \random_int(0, \mt_getrandmax()) .
2281 1
                        \session_id() .
2282 1
                        ($_SERVER['REMOTE_ADDR'] ?? '') .
2283 1
                        ($_SERVER['SERVER_ADDR'] ?? '') .
2284 1
                        $entropy_extra;
2285
2286 1
        $unique_string = \uniqid($unique_helper, true);
2287
2288 1
        if ($use_md5) {
2289 1
            $unique_string = \md5($unique_string . $unique_helper);
2290
        }
2291
2292 1
        return $unique_string;
2293
    }
2294
2295
    /**
2296
     * alias for "UTF8::string_has_bom()"
2297
     *
2298
     * @param string $str
2299
     *
2300
     * @return bool
2301
     *
2302
     * @see UTF8::string_has_bom()
2303
     * @deprecated <p>please use "UTF8::string_has_bom()"</p>
2304
     */
2305 2
    public static function hasBom(string $str): bool
2306
    {
2307 2
        return self::string_has_bom($str);
2308
    }
2309
2310
    /**
2311
     * Returns true if the string contains a lower case char, false otherwise.
2312
     *
2313
     * @param string $str <p>The input string.</p>
2314
     *
2315
     * @return bool whether or not the string contains a lower case character
2316
     */
2317 47
    public static function has_lowercase(string $str): bool
2318
    {
2319 47
        if (self::$SUPPORT['mbstring'] === true) {
2320
            /** @noinspection PhpComposerExtensionStubsInspection */
2321 47
            return \mb_ereg_match('.*[[:lower:]]', $str);
2322
        }
2323
2324
        return self::str_matches_pattern($str, '.*[[:lower:]]');
2325
    }
2326
2327
    /**
2328
     * Returns true if the string contains an upper case char, false otherwise.
2329
     *
2330
     * @param string $str <p>The input string.</p>
2331
     *
2332
     * @return bool whether or not the string contains an upper case character
2333
     */
2334 12
    public static function has_uppercase(string $str): bool
2335
    {
2336 12
        if (self::$SUPPORT['mbstring'] === true) {
2337
            /** @noinspection PhpComposerExtensionStubsInspection */
2338 12
            return \mb_ereg_match('.*[[:upper:]]', $str);
2339
        }
2340
2341
        return self::str_matches_pattern($str, '.*[[:upper:]]');
2342
    }
2343
2344
    /**
2345
     * Converts a hexadecimal value into a UTF-8 character.
2346
     *
2347
     * @param string $hexdec <p>The hexadecimal value.</p>
2348
     *
2349
     * @return false|string one single UTF-8 character
2350
     */
2351 4
    public static function hex_to_chr(string $hexdec)
2352
    {
2353 4
        return self::decimal_to_chr(\hexdec($hexdec));
2354
    }
2355
2356
    /**
2357
     * Converts hexadecimal U+xxxx code point representation to integer.
2358
     *
2359
     * INFO: opposite to UTF8::int_to_hex()
2360
     *
2361
     * @param string $hexdec <p>The hexadecimal code point representation.</p>
2362
     *
2363
     * @return false|int the code point, or false on failure
2364
     */
2365 2
    public static function hex_to_int($hexdec)
2366
    {
2367
        // init
2368 2
        $hexdec = (string) $hexdec;
2369
2370 2
        if ($hexdec === '') {
2371 2
            return false;
2372
        }
2373
2374 2
        if (\preg_match('/^(?:\\\u|U\+|)([a-zA-Z0-9]{4,6})$/', $hexdec, $match)) {
2375 2
            return \intval($match[1], 16);
2376
        }
2377
2378 2
        return false;
2379
    }
2380
2381
    /**
2382
     * alias for "UTF8::html_entity_decode()"
2383
     *
2384
     * @param string $str
2385
     * @param int    $flags
2386
     * @param string $encoding
2387
     *
2388
     * @return string
2389
     *
2390
     * @see UTF8::html_entity_decode()
2391
     * @deprecated <p>please use "UTF8::html_entity_decode()"</p>
2392
     */
2393 2
    public static function html_decode(
2394
        string $str,
2395
        int $flags = null,
2396
        string $encoding = 'UTF-8'
2397
    ): string {
2398 2
        return self::html_entity_decode($str, $flags, $encoding);
2399
    }
2400
2401
    /**
2402
     * Converts a UTF-8 string to a series of HTML numbered entities.
2403
     *
2404
     * INFO: opposite to UTF8::html_decode()
2405
     *
2406
     * @param string $str              <p>The Unicode string to be encoded as numbered entities.</p>
2407
     * @param bool   $keep_ascii_chars [optional] <p>Keep ASCII chars.</p>
2408
     * @param string $encoding         [optional] <p>Set the charset for e.g. "mb_" function</p>
2409
     *
2410
     * @return string HTML numbered entities
2411
     */
2412 14
    public static function html_encode(
2413
        string $str,
2414
        bool $keep_ascii_chars = false,
2415
        string $encoding = 'UTF-8'
2416
    ): string {
2417 14
        if ($str === '') {
2418 4
            return '';
2419
        }
2420
2421 14
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
2422 4
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
2423
        }
2424
2425
        // INFO: http://stackoverflow.com/questions/35854535/better-explanation-of-convmap-in-mb-encode-numericentity
2426 14
        if (self::$SUPPORT['mbstring'] === true) {
2427 14
            $start_code = 0x00;
2428 14
            if ($keep_ascii_chars === true) {
2429 13
                $start_code = 0x80;
2430
            }
2431
2432 14
            if ($encoding === 'UTF-8') {
2433
                /** @var false|string|null $return - needed for PhpStan (stubs error) */
2434 14
                $return = \mb_encode_numericentity(
2435 14
                    $str,
2436 14
                    [$start_code, 0xfffff, 0, 0xfffff, 0]
2437
                );
2438 14
                if ($return !== null && $return !== false) {
2439 14
                    return $return;
2440
                }
2441
            }
2442
2443
            /** @var false|string|null $return - needed for PhpStan (stubs error) */
2444 4
            $return = \mb_encode_numericentity(
2445 4
                $str,
2446 4
                [$start_code, 0xfffff, 0, 0xfffff, 0],
2447 4
                $encoding
2448
            );
2449 4
            if ($return !== null && $return !== false) {
2450 4
                return $return;
2451
            }
2452
        }
2453
2454
        //
2455
        // fallback via vanilla php
2456
        //
2457
2458
        return \implode(
2459
            '',
2460
            \array_map(
2461
                static function (string $chr) use ($keep_ascii_chars, $encoding): string {
2462
                    return self::single_chr_html_encode($chr, $keep_ascii_chars, $encoding);
2463
                },
2464
                self::str_split($str)
2465
            )
2466
        );
2467
    }
2468
2469
    /**
2470
     * UTF-8 version of html_entity_decode()
2471
     *
2472
     * The reason we are not using html_entity_decode() by itself is because
2473
     * while it is not technically correct to leave out the semicolon
2474
     * at the end of an entity most browsers will still interpret the entity
2475
     * correctly. html_entity_decode() does not convert entities without
2476
     * semicolons, so we are left with our own little solution here. Bummer.
2477
     *
2478
     * Convert all HTML entities to their applicable characters
2479
     *
2480
     * INFO: opposite to UTF8::html_encode()
2481
     *
2482
     * @see http://php.net/manual/en/function.html-entity-decode.php
2483
     *
2484
     * @param string $str      <p>
2485
     *                         The input string.
2486
     *                         </p>
2487
     * @param int    $flags    [optional] <p>
2488
     *                         A bitmask of one or more of the following flags, which specify how to handle quotes
2489
     *                         and which document type to use. The default is ENT_COMPAT | ENT_HTML401.
2490
     *                         <table>
2491
     *                         Available <i>flags</i> constants
2492
     *                         <tr valign="top">
2493
     *                         <td>Constant Name</td>
2494
     *                         <td>Description</td>
2495
     *                         </tr>
2496
     *                         <tr valign="top">
2497
     *                         <td><b>ENT_COMPAT</b></td>
2498
     *                         <td>Will convert double-quotes and leave single-quotes alone.</td>
2499
     *                         </tr>
2500
     *                         <tr valign="top">
2501
     *                         <td><b>ENT_QUOTES</b></td>
2502
     *                         <td>Will convert both double and single quotes.</td>
2503
     *                         </tr>
2504
     *                         <tr valign="top">
2505
     *                         <td><b>ENT_NOQUOTES</b></td>
2506
     *                         <td>Will leave both double and single quotes unconverted.</td>
2507
     *                         </tr>
2508
     *                         <tr valign="top">
2509
     *                         <td><b>ENT_HTML401</b></td>
2510
     *                         <td>
2511
     *                         Handle code as HTML 4.01.
2512
     *                         </td>
2513
     *                         </tr>
2514
     *                         <tr valign="top">
2515
     *                         <td><b>ENT_XML1</b></td>
2516
     *                         <td>
2517
     *                         Handle code as XML 1.
2518
     *                         </td>
2519
     *                         </tr>
2520
     *                         <tr valign="top">
2521
     *                         <td><b>ENT_XHTML</b></td>
2522
     *                         <td>
2523
     *                         Handle code as XHTML.
2524
     *                         </td>
2525
     *                         </tr>
2526
     *                         <tr valign="top">
2527
     *                         <td><b>ENT_HTML5</b></td>
2528
     *                         <td>
2529
     *                         Handle code as HTML 5.
2530
     *                         </td>
2531
     *                         </tr>
2532
     *                         </table>
2533
     *                         </p>
2534
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
2535
     *
2536
     * @return string the decoded string
2537
     */
2538 46
    public static function html_entity_decode(
2539
        string $str,
2540
        int $flags = null,
2541
        string $encoding = 'UTF-8'
2542
    ): string {
2543
        if (
2544 46
            !isset($str[3]) // examples: &; || &x;
2545
            ||
2546 46
            \strpos($str, '&') === false // no "&"
2547
        ) {
2548 23
            return $str;
2549
        }
2550
2551 44
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
2552 9
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
2553
        }
2554
2555 44
        if ($flags === null) {
2556 10
            $flags = \ENT_QUOTES | \ENT_HTML5;
2557
        }
2558
2559
        if (
2560 44
            $encoding !== 'UTF-8'
2561
            &&
2562 44
            $encoding !== 'ISO-8859-1'
2563
            &&
2564 44
            $encoding !== 'WINDOWS-1252'
2565
            &&
2566 44
            self::$SUPPORT['mbstring'] === false
2567
        ) {
2568
            \trigger_error('UTF8::html_entity_decode() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
2569
        }
2570
2571
        do {
2572 44
            $str_compare = $str;
2573
2574 44
            if (\strpos($str, '&') !== false) {
2575 44
                if (\strpos($str, '&#') !== false) {
2576
                    // decode also numeric & UTF16 two byte entities
2577 36
                    $str = (string) \preg_replace(
2578 36
                        '/(&#(?:x0*[0-9a-fA-F]{2,6}(?![0-9a-fA-F;])|(?:0*\d{2,6}(?![0-9;]))))/S',
2579 36
                        '$1;',
2580 36
                        $str
2581
                    );
2582
                }
2583
2584 44
                $str = \html_entity_decode(
2585 44
                    $str,
2586 44
                    $flags,
2587 44
                    $encoding
2588
                );
2589
            }
2590 44
        } while ($str_compare !== $str);
2591
2592 44
        return $str;
2593
    }
2594
2595
    /**
2596
     * Create a escape html version of the string via "UTF8::htmlspecialchars()".
2597
     *
2598
     * @param string $str
2599
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
2600
     *
2601
     * @return string
2602
     */
2603 6
    public static function html_escape(string $str, string $encoding = 'UTF-8'): string
2604
    {
2605 6
        return self::htmlspecialchars(
2606 6
            $str,
2607 6
            \ENT_QUOTES | \ENT_SUBSTITUTE,
2608 6
            $encoding
2609
        );
2610
    }
2611
2612
    /**
2613
     * Remove empty html-tag.
2614
     *
2615
     * e.g.: <tag></tag>
2616
     *
2617
     * @param string $str
2618
     *
2619
     * @return string
2620
     */
2621 1
    public static function html_stripe_empty_tags(string $str): string
2622
    {
2623 1
        return (string) \preg_replace(
2624 1
            '/<[^\\/>]*?>\\s*?<\\/[^>]*?>/u',
2625 1
            '',
2626 1
            $str
2627
        );
2628
    }
2629
2630
    /**
2631
     * Convert all applicable characters to HTML entities: UTF-8 version of htmlentities()
2632
     *
2633
     * @see http://php.net/manual/en/function.htmlentities.php
2634
     *
2635
     * @param string $str           <p>
2636
     *                              The input string.
2637
     *                              </p>
2638
     * @param int    $flags         [optional] <p>
2639
     *                              A bitmask of one or more of the following flags, which specify how to handle
2640
     *                              quotes, invalid code unit sequences and the used document type. The default is
2641
     *                              ENT_COMPAT | ENT_HTML401.
2642
     *                              <table>
2643
     *                              Available <i>flags</i> constants
2644
     *                              <tr valign="top">
2645
     *                              <td>Constant Name</td>
2646
     *                              <td>Description</td>
2647
     *                              </tr>
2648
     *                              <tr valign="top">
2649
     *                              <td><b>ENT_COMPAT</b></td>
2650
     *                              <td>Will convert double-quotes and leave single-quotes alone.</td>
2651
     *                              </tr>
2652
     *                              <tr valign="top">
2653
     *                              <td><b>ENT_QUOTES</b></td>
2654
     *                              <td>Will convert both double and single quotes.</td>
2655
     *                              </tr>
2656
     *                              <tr valign="top">
2657
     *                              <td><b>ENT_NOQUOTES</b></td>
2658
     *                              <td>Will leave both double and single quotes unconverted.</td>
2659
     *                              </tr>
2660
     *                              <tr valign="top">
2661
     *                              <td><b>ENT_IGNORE</b></td>
2662
     *                              <td>
2663
     *                              Silently discard invalid code unit sequences instead of returning
2664
     *                              an empty string. Using this flag is discouraged as it
2665
     *                              may have security implications.
2666
     *                              </td>
2667
     *                              </tr>
2668
     *                              <tr valign="top">
2669
     *                              <td><b>ENT_SUBSTITUTE</b></td>
2670
     *                              <td>
2671
     *                              Replace invalid code unit sequences with a Unicode Replacement Character
2672
     *                              U+FFFD (UTF-8) or &#38;#38;#FFFD; (otherwise) instead of returning an empty
2673
     *                              string.
2674
     *                              </td>
2675
     *                              </tr>
2676
     *                              <tr valign="top">
2677
     *                              <td><b>ENT_DISALLOWED</b></td>
2678
     *                              <td>
2679
     *                              Replace invalid code points for the given document type with a
2680
     *                              Unicode Replacement Character U+FFFD (UTF-8) or &#38;#38;#FFFD;
2681
     *                              (otherwise) instead of leaving them as is. This may be useful, for
2682
     *                              instance, to ensure the well-formedness of XML documents with
2683
     *                              embedded external content.
2684
     *                              </td>
2685
     *                              </tr>
2686
     *                              <tr valign="top">
2687
     *                              <td><b>ENT_HTML401</b></td>
2688
     *                              <td>
2689
     *                              Handle code as HTML 4.01.
2690
     *                              </td>
2691
     *                              </tr>
2692
     *                              <tr valign="top">
2693
     *                              <td><b>ENT_XML1</b></td>
2694
     *                              <td>
2695
     *                              Handle code as XML 1.
2696
     *                              </td>
2697
     *                              </tr>
2698
     *                              <tr valign="top">
2699
     *                              <td><b>ENT_XHTML</b></td>
2700
     *                              <td>
2701
     *                              Handle code as XHTML.
2702
     *                              </td>
2703
     *                              </tr>
2704
     *                              <tr valign="top">
2705
     *                              <td><b>ENT_HTML5</b></td>
2706
     *                              <td>
2707
     *                              Handle code as HTML 5.
2708
     *                              </td>
2709
     *                              </tr>
2710
     *                              </table>
2711
     *                              </p>
2712
     * @param string $encoding      [optional] <p>
2713
     *                              Like <b>htmlspecialchars</b>,
2714
     *                              <b>htmlentities</b> takes an optional third argument
2715
     *                              <i>encoding</i> which defines encoding used in
2716
     *                              conversion.
2717
     *                              Although this argument is technically optional, you are highly
2718
     *                              encouraged to specify the correct value for your code.
2719
     *                              </p>
2720
     * @param bool   $double_encode [optional] <p>
2721
     *                              When <i>double_encode</i> is turned off PHP will not
2722
     *                              encode existing html entities. The default is to convert everything.
2723
     *                              </p>
2724
     *
2725
     * @return string
2726
     *                <p>
2727
     *                The encoded string.
2728
     *                <br><br>
2729
     *                If the input <i>string</i> contains an invalid code unit
2730
     *                sequence within the given <i>encoding</i> an empty string
2731
     *                will be returned, unless either the <b>ENT_IGNORE</b> or
2732
     *                <b>ENT_SUBSTITUTE</b> flags are set.
2733
     *                </p>
2734
     */
2735 9
    public static function htmlentities(
2736
        string $str,
2737
        int $flags = \ENT_COMPAT,
2738
        string $encoding = 'UTF-8',
2739
        bool $double_encode = true
2740
    ): string {
2741 9
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
2742 7
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
2743
        }
2744
2745 9
        $str = \htmlentities(
2746 9
            $str,
2747 9
            $flags,
2748 9
            $encoding,
2749 9
            $double_encode
2750
        );
2751
2752
        /**
2753
         * PHP doesn't replace a backslash to its html entity since this is something
2754
         * that's mostly used to escape characters when inserting in a database. Since
2755
         * we're using a decent database layer, we don't need this shit and we're replacing
2756
         * the double backslashes by its' html entity equivalent.
2757
         *
2758
         * https://github.com/forkcms/library/blob/master/spoon/filter/filter.php#L303
2759
         */
2760 9
        $str = \str_replace('\\', '&#92;', $str);
2761
2762 9
        return self::html_encode($str, true, $encoding);
2763
    }
2764
2765
    /**
2766
     * Convert only special characters to HTML entities: UTF-8 version of htmlspecialchars()
2767
     *
2768
     * INFO: Take a look at "UTF8::htmlentities()"
2769
     *
2770
     * @see http://php.net/manual/en/function.htmlspecialchars.php
2771
     *
2772
     * @param string $str           <p>
2773
     *                              The string being converted.
2774
     *                              </p>
2775
     * @param int    $flags         [optional] <p>
2776
     *                              A bitmask of one or more of the following flags, which specify how to handle
2777
     *                              quotes, invalid code unit sequences and the used document type. The default is
2778
     *                              ENT_COMPAT | ENT_HTML401.
2779
     *                              <table>
2780
     *                              Available <i>flags</i> constants
2781
     *                              <tr valign="top">
2782
     *                              <td>Constant Name</td>
2783
     *                              <td>Description</td>
2784
     *                              </tr>
2785
     *                              <tr valign="top">
2786
     *                              <td><b>ENT_COMPAT</b></td>
2787
     *                              <td>Will convert double-quotes and leave single-quotes alone.</td>
2788
     *                              </tr>
2789
     *                              <tr valign="top">
2790
     *                              <td><b>ENT_QUOTES</b></td>
2791
     *                              <td>Will convert both double and single quotes.</td>
2792
     *                              </tr>
2793
     *                              <tr valign="top">
2794
     *                              <td><b>ENT_NOQUOTES</b></td>
2795
     *                              <td>Will leave both double and single quotes unconverted.</td>
2796
     *                              </tr>
2797
     *                              <tr valign="top">
2798
     *                              <td><b>ENT_IGNORE</b></td>
2799
     *                              <td>
2800
     *                              Silently discard invalid code unit sequences instead of returning
2801
     *                              an empty string. Using this flag is discouraged as it
2802
     *                              may have security implications.
2803
     *                              </td>
2804
     *                              </tr>
2805
     *                              <tr valign="top">
2806
     *                              <td><b>ENT_SUBSTITUTE</b></td>
2807
     *                              <td>
2808
     *                              Replace invalid code unit sequences with a Unicode Replacement Character
2809
     *                              U+FFFD (UTF-8) or &#38;#38;#FFFD; (otherwise) instead of returning an empty
2810
     *                              string.
2811
     *                              </td>
2812
     *                              </tr>
2813
     *                              <tr valign="top">
2814
     *                              <td><b>ENT_DISALLOWED</b></td>
2815
     *                              <td>
2816
     *                              Replace invalid code points for the given document type with a
2817
     *                              Unicode Replacement Character U+FFFD (UTF-8) or &#38;#38;#FFFD;
2818
     *                              (otherwise) instead of leaving them as is. This may be useful, for
2819
     *                              instance, to ensure the well-formedness of XML documents with
2820
     *                              embedded external content.
2821
     *                              </td>
2822
     *                              </tr>
2823
     *                              <tr valign="top">
2824
     *                              <td><b>ENT_HTML401</b></td>
2825
     *                              <td>
2826
     *                              Handle code as HTML 4.01.
2827
     *                              </td>
2828
     *                              </tr>
2829
     *                              <tr valign="top">
2830
     *                              <td><b>ENT_XML1</b></td>
2831
     *                              <td>
2832
     *                              Handle code as XML 1.
2833
     *                              </td>
2834
     *                              </tr>
2835
     *                              <tr valign="top">
2836
     *                              <td><b>ENT_XHTML</b></td>
2837
     *                              <td>
2838
     *                              Handle code as XHTML.
2839
     *                              </td>
2840
     *                              </tr>
2841
     *                              <tr valign="top">
2842
     *                              <td><b>ENT_HTML5</b></td>
2843
     *                              <td>
2844
     *                              Handle code as HTML 5.
2845
     *                              </td>
2846
     *                              </tr>
2847
     *                              </table>
2848
     *                              </p>
2849
     * @param string $encoding      [optional] <p>
2850
     *                              Defines encoding used in conversion.
2851
     *                              </p>
2852
     *                              <p>
2853
     *                              For the purposes of this function, the encodings
2854
     *                              ISO-8859-1, ISO-8859-15,
2855
     *                              UTF-8, cp866,
2856
     *                              cp1251, cp1252, and
2857
     *                              KOI8-R are effectively equivalent, provided the
2858
     *                              <i>string</i> itself is valid for the encoding, as
2859
     *                              the characters affected by <b>htmlspecialchars</b> occupy
2860
     *                              the same positions in all of these encodings.
2861
     *                              </p>
2862
     * @param bool   $double_encode [optional] <p>
2863
     *                              When <i>double_encode</i> is turned off PHP will not
2864
     *                              encode existing html entities, the default is to convert everything.
2865
     *                              </p>
2866
     *
2867
     * @return string the converted string.
2868
     *                </p>
2869
     *                <p>
2870
     *                If the input <i>string</i> contains an invalid code unit
2871
     *                sequence within the given <i>encoding</i> an empty string
2872
     *                will be returned, unless either the <b>ENT_IGNORE</b> or
2873
     *                <b>ENT_SUBSTITUTE</b> flags are set
2874
     */
2875 8
    public static function htmlspecialchars(
2876
        string $str,
2877
        int $flags = \ENT_COMPAT,
2878
        string $encoding = 'UTF-8',
2879
        bool $double_encode = true
2880
    ): string {
2881 8
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
2882 8
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
2883
        }
2884
2885 8
        return \htmlspecialchars(
2886 8
            $str,
2887 8
            $flags,
2888 8
            $encoding,
2889 8
            $double_encode
2890
        );
2891
    }
2892
2893
    /**
2894
     * Checks whether iconv is available on the server.
2895
     *
2896
     * @return bool
2897
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
2898
     */
2899
    public static function iconv_loaded(): bool
2900
    {
2901
        return \extension_loaded('iconv');
2902
    }
2903
2904
    /**
2905
     * alias for "UTF8::decimal_to_chr()"
2906
     *
2907
     * @param mixed $int
2908
     *
2909
     * @return string
2910
     *
2911
     * @see UTF8::decimal_to_chr()
2912
     * @deprecated <p>please use "UTF8::decimal_to_chr()"</p>
2913
     */
2914 4
    public static function int_to_chr($int): string
2915
    {
2916 4
        return self::decimal_to_chr($int);
2917
    }
2918
2919
    /**
2920
     * Converts Integer to hexadecimal U+xxxx code point representation.
2921
     *
2922
     * INFO: opposite to UTF8::hex_to_int()
2923
     *
2924
     * @param int    $int    <p>The integer to be converted to hexadecimal code point.</p>
2925
     * @param string $prefix [optional]
2926
     *
2927
     * @return string the code point, or empty string on failure
2928
     */
2929 6
    public static function int_to_hex(int $int, string $prefix = 'U+'): string
2930
    {
2931 6
        $hex = \dechex($int);
2932
2933 6
        $hex = (\strlen($hex) < 4 ? \substr('0000' . $hex, -4) : $hex);
2934
2935 6
        return $prefix . $hex . '';
2936
    }
2937
2938
    /**
2939
     * Checks whether intl-char is available on the server.
2940
     *
2941
     * @return bool
2942
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
2943
     */
2944
    public static function intlChar_loaded(): bool
2945
    {
2946
        return \class_exists('IntlChar');
2947
    }
2948
2949
    /**
2950
     * Checks whether intl is available on the server.
2951
     *
2952
     * @return bool
2953
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
2954
     */
2955 5
    public static function intl_loaded(): bool
2956
    {
2957 5
        return \extension_loaded('intl');
2958
    }
2959
2960
    /**
2961
     * alias for "UTF8::is_ascii()"
2962
     *
2963
     * @param string $str
2964
     *
2965
     * @return bool
2966
     *
2967
     * @see UTF8::is_ascii()
2968
     * @deprecated <p>please use "UTF8::is_ascii()"</p>
2969
     */
2970 2
    public static function isAscii(string $str): bool
2971
    {
2972 2
        return ASCII::is_ascii($str);
2973
    }
2974
2975
    /**
2976
     * alias for "UTF8::is_base64()"
2977
     *
2978
     * @param string $str
2979
     *
2980
     * @return bool
2981
     *
2982
     * @see UTF8::is_base64()
2983
     * @deprecated <p>please use "UTF8::is_base64()"</p>
2984
     */
2985 2
    public static function isBase64($str): bool
2986
    {
2987 2
        return self::is_base64($str);
2988
    }
2989
2990
    /**
2991
     * alias for "UTF8::is_binary()"
2992
     *
2993
     * @param mixed $str
2994
     * @param bool  $strict
2995
     *
2996
     * @return bool
2997
     *
2998
     * @see UTF8::is_binary()
2999
     * @deprecated <p>please use "UTF8::is_binary()"</p>
3000
     */
3001 4
    public static function isBinary($str, $strict = false): bool
3002
    {
3003 4
        return self::is_binary($str, $strict);
3004
    }
3005
3006
    /**
3007
     * alias for "UTF8::is_bom()"
3008
     *
3009
     * @param string $utf8_chr
3010
     *
3011
     * @return bool
3012
     *
3013
     * @see UTF8::is_bom()
3014
     * @deprecated <p>please use "UTF8::is_bom()"</p>
3015
     */
3016 2
    public static function isBom(string $utf8_chr): bool
3017
    {
3018 2
        return self::is_bom($utf8_chr);
3019
    }
3020
3021
    /**
3022
     * alias for "UTF8::is_html()"
3023
     *
3024
     * @param string $str
3025
     *
3026
     * @return bool
3027
     *
3028
     * @see UTF8::is_html()
3029
     * @deprecated <p>please use "UTF8::is_html()"</p>
3030
     */
3031 2
    public static function isHtml(string $str): bool
3032
    {
3033 2
        return self::is_html($str);
3034
    }
3035
3036
    /**
3037
     * alias for "UTF8::is_json()"
3038
     *
3039
     * @param string $str
3040
     *
3041
     * @return bool
3042
     *
3043
     * @see UTF8::is_json()
3044
     * @deprecated <p>please use "UTF8::is_json()"</p>
3045
     */
3046
    public static function isJson(string $str): bool
3047
    {
3048
        return self::is_json($str);
3049
    }
3050
3051
    /**
3052
     * alias for "UTF8::is_utf16()"
3053
     *
3054
     * @param mixed $str
3055
     *
3056
     * @return false|int
3057
     *                   <strong>false</strong> if is't not UTF16,<br>
3058
     *                   <strong>1</strong> for UTF-16LE,<br>
3059
     *                   <strong>2</strong> for UTF-16BE
3060
     *
3061
     * @see UTF8::is_utf16()
3062
     * @deprecated <p>please use "UTF8::is_utf16()"</p>
3063
     */
3064 2
    public static function isUtf16($str)
3065
    {
3066 2
        return self::is_utf16($str);
3067
    }
3068
3069
    /**
3070
     * alias for "UTF8::is_utf32()"
3071
     *
3072
     * @param mixed $str
3073
     *
3074
     * @return false|int
3075
     *                   <strong>false</strong> if is't not UTF16,
3076
     *                   <strong>1</strong> for UTF-32LE,
3077
     *                   <strong>2</strong> for UTF-32BE
3078
     *
3079
     * @see UTF8::is_utf32()
3080
     * @deprecated <p>please use "UTF8::is_utf32()"</p>
3081
     */
3082 2
    public static function isUtf32($str)
3083
    {
3084 2
        return self::is_utf32($str);
3085
    }
3086
3087
    /**
3088
     * alias for "UTF8::is_utf8()"
3089
     *
3090
     * @param string $str
3091
     * @param bool   $strict
3092
     *
3093
     * @return bool
3094
     *
3095
     * @see UTF8::is_utf8()
3096
     * @deprecated <p>please use "UTF8::is_utf8()"</p>
3097
     */
3098 17
    public static function isUtf8($str, $strict = false): bool
3099
    {
3100 17
        return self::is_utf8($str, $strict);
3101
    }
3102
3103
    /**
3104
     * Returns true if the string contains only alphabetic chars, false otherwise.
3105
     *
3106
     * @param string $str
3107
     *
3108
     * @return bool
3109
     *              Whether or not $str contains only alphabetic chars
3110
     */
3111 10
    public static function is_alpha(string $str): bool
3112
    {
3113 10
        if (self::$SUPPORT['mbstring'] === true) {
3114
            /** @noinspection PhpComposerExtensionStubsInspection */
3115 10
            return \mb_ereg_match('^[[:alpha:]]*$', $str);
3116
        }
3117
3118
        return self::str_matches_pattern($str, '^[[:alpha:]]*$');
3119
    }
3120
3121
    /**
3122
     * Returns true if the string contains only alphabetic and numeric chars, false otherwise.
3123
     *
3124
     * @param string $str
3125
     *
3126
     * @return bool
3127
     *              Whether or not $str contains only alphanumeric chars
3128
     */
3129 13
    public static function is_alphanumeric(string $str): bool
3130
    {
3131 13
        if (self::$SUPPORT['mbstring'] === true) {
3132
            /** @noinspection PhpComposerExtensionStubsInspection */
3133 13
            return \mb_ereg_match('^[[:alnum:]]*$', $str);
3134
        }
3135
3136
        return self::str_matches_pattern($str, '^[[:alnum:]]*$');
3137
    }
3138
3139
    /**
3140
     * Checks if a string is 7 bit ASCII.
3141
     *
3142
     * @param string $str <p>The string to check.</p>
3143
     *
3144
     * @return bool
3145
     *              <strong>true</strong> if it is ASCII<br>
3146
     *              <strong>false</strong> otherwise
3147
     */
3148 8
    public static function is_ascii(string $str): bool
3149
    {
3150 8
        return ASCII::is_ascii($str);
3151
    }
3152
3153
    /**
3154
     * Returns true if the string is base64 encoded, false otherwise.
3155
     *
3156
     * @param mixed|string $str                   <p>The input string.</p>
3157
     * @param bool         $empty_string_is_valid [optional] <p>Is an empty string valid base64 or not?</p>
3158
     *
3159
     * @return bool whether or not $str is base64 encoded
3160
     */
3161 16
    public static function is_base64($str, $empty_string_is_valid = false): bool
3162
    {
3163
        if (
3164 16
            $empty_string_is_valid === false
3165
            &&
3166 16
            $str === ''
3167
        ) {
3168 3
            return false;
3169
        }
3170
3171
        /**
3172
         * @psalm-suppress RedundantConditionGivenDocblockType
3173
         */
3174 15
        if (\is_string($str) === false) {
3175 2
            return false;
3176
        }
3177
3178 15
        $base64String = \base64_decode($str, true);
3179
3180 15
        return $base64String !== false && \base64_encode($base64String) === $str;
3181
    }
3182
3183
    /**
3184
     * Check if the input is binary... (is look like a hack).
3185
     *
3186
     * @param mixed $input
3187
     * @param bool  $strict
3188
     *
3189
     * @return bool
3190
     */
3191 39
    public static function is_binary($input, bool $strict = false): bool
3192
    {
3193 39
        $input = (string) $input;
3194 39
        if ($input === '') {
3195 10
            return false;
3196
        }
3197
3198 39
        if (\preg_match('~^[01]+$~', $input)) {
3199 13
            return true;
3200
        }
3201
3202 39
        $ext = self::get_file_type($input);
3203 39
        if ($ext['type'] === 'binary') {
3204 7
            return true;
3205
        }
3206
3207 38
        $test_length = \strlen($input);
3208 38
        $test_null_counting = \substr_count($input, "\x0", 0, $test_length);
3209 38
        if (($test_null_counting / $test_length) > 0.25) {
3210 15
            return true;
3211
        }
3212
3213 34
        if ($strict === true) {
3214 34
            if (self::$SUPPORT['finfo'] === false) {
3215
                throw new \RuntimeException('ext-fileinfo: is not installed');
3216
            }
3217
3218
            /** @noinspection PhpComposerExtensionStubsInspection */
3219 34
            $finfo_encoding = (new \finfo(\FILEINFO_MIME_ENCODING))->buffer($input);
3220 34
            if ($finfo_encoding && $finfo_encoding === 'binary') {
3221 15
                return true;
3222
            }
3223
        }
3224
3225 30
        return false;
3226
    }
3227
3228
    /**
3229
     * Check if the file is binary.
3230
     *
3231
     * @param string $file
3232
     *
3233
     * @return bool
3234
     */
3235 6
    public static function is_binary_file($file): bool
3236
    {
3237
        // init
3238 6
        $block = '';
3239
3240 6
        $fp = \fopen($file, 'rb');
3241 6
        if (\is_resource($fp)) {
3242 6
            $block = \fread($fp, 512);
3243 6
            \fclose($fp);
3244
        }
3245
3246 6
        if ($block === '') {
3247 2
            return false;
3248
        }
3249
3250 6
        return self::is_binary($block, true);
3251
    }
3252
3253
    /**
3254
     * Returns true if the string contains only whitespace chars, false otherwise.
3255
     *
3256
     * @param string $str
3257
     *
3258
     * @return bool
3259
     *              Whether or not $str contains only whitespace characters
3260
     */
3261 15
    public static function is_blank(string $str): bool
3262
    {
3263 15
        if (self::$SUPPORT['mbstring'] === true) {
3264
            /** @noinspection PhpComposerExtensionStubsInspection */
3265 15
            return \mb_ereg_match('^[[:space:]]*$', $str);
3266
        }
3267
3268
        return self::str_matches_pattern($str, '^[[:space:]]*$');
3269
    }
3270
3271
    /**
3272
     * Checks if the given string is equal to any "Byte Order Mark".
3273
     *
3274
     * WARNING: Use "UTF8::string_has_bom()" if you will check BOM in a string.
3275
     *
3276
     * @param string $str <p>The input string.</p>
3277
     *
3278
     * @return bool
3279
     *              <strong>true</strong> if the $utf8_chr is Byte Order Mark, <strong>false</strong> otherwise
3280
     */
3281 2
    public static function is_bom($str): bool
3282
    {
3283
        /** @noinspection PhpUnusedLocalVariableInspection */
3284 2
        foreach (self::$BOM as $bom_string => &$bom_byte_length) {
3285 2
            if ($str === $bom_string) {
3286 2
                return true;
3287
            }
3288
        }
3289
3290 2
        return false;
3291
    }
3292
3293
    /**
3294
     * Determine whether the string is considered to be empty.
3295
     *
3296
     * A variable is considered empty if it does not exist or if its value equals FALSE.
3297
     * empty() does not generate a warning if the variable does not exist.
3298
     *
3299
     * @param mixed $str
3300
     *
3301
     * @return bool whether or not $str is empty()
3302
     */
3303
    public static function is_empty($str): bool
3304
    {
3305
        return empty($str);
3306
    }
3307
3308
    /**
3309
     * Returns true if the string contains only hexadecimal chars, false otherwise.
3310
     *
3311
     * @param string $str
3312
     *
3313
     * @return bool
3314
     *              Whether or not $str contains only hexadecimal chars
3315
     */
3316 13
    public static function is_hexadecimal(string $str): bool
3317
    {
3318 13
        if (self::$SUPPORT['mbstring'] === true) {
3319
            /** @noinspection PhpComposerExtensionStubsInspection */
3320 13
            return \mb_ereg_match('^[[:xdigit:]]*$', $str);
3321
        }
3322
3323
        return self::str_matches_pattern($str, '^[[:xdigit:]]*$');
3324
    }
3325
3326
    /**
3327
     * Check if the string contains any HTML tags.
3328
     *
3329
     * @param string $str <p>The input string.</p>
3330
     *
3331
     * @return bool
3332
     */
3333 3
    public static function is_html(string $str): bool
3334
    {
3335 3
        if ($str === '') {
3336 3
            return false;
3337
        }
3338
3339
        // init
3340 3
        $matches = [];
3341
3342 3
        $str = self::emoji_encode($str); // hack for emoji support :/
3343
3344 3
        \preg_match("/<\\/?\\w+(?:(?:\\s+\\w+(?:\\s*=\\s*(?:\".*?\"|'.*?'|[^'\">\\s]+))?)*\\s*|\\s*)\\/?>/u", $str, $matches);
3345
3346 3
        return $matches !== [];
3347
    }
3348
3349
    /**
3350
     * Try to check if "$str" is a JSON-string.
3351
     *
3352
     * @param string $str                                    <p>The input string.</p>
3353
     * @param bool   $only_array_or_object_results_are_valid [optional] <p>Only array and objects are valid json results.</p>
3354
     *
3355
     * @return bool
3356
     */
3357 42
    public static function is_json(
3358
        string $str,
3359
        $only_array_or_object_results_are_valid = true
3360
    ): bool {
3361 42
        if ($str === '') {
3362 4
            return false;
3363
        }
3364
3365 40
        if (self::$SUPPORT['json'] === false) {
3366
            throw new \RuntimeException('ext-json: is not installed');
3367
        }
3368
3369 40
        $json = self::json_decode($str);
3370 40
        if ($json === null && \strtoupper($str) !== 'NULL') {
3371 18
            return false;
3372
        }
3373
3374
        if (
3375 24
            $only_array_or_object_results_are_valid === true
3376
            &&
3377 24
            \is_object($json) === false
3378
            &&
3379 24
            \is_array($json) === false
3380
        ) {
3381 5
            return false;
3382
        }
3383
3384
        /** @noinspection PhpComposerExtensionStubsInspection */
3385 19
        return \json_last_error() === \JSON_ERROR_NONE;
3386
    }
3387
3388
    /**
3389
     * @param string $str
3390
     *
3391
     * @return bool
3392
     */
3393 8
    public static function is_lowercase(string $str): bool
3394
    {
3395 8
        if (self::$SUPPORT['mbstring'] === true) {
3396
            /** @noinspection PhpComposerExtensionStubsInspection */
3397 8
            return \mb_ereg_match('^[[:lower:]]*$', $str);
3398
        }
3399
3400
        return self::str_matches_pattern($str, '^[[:lower:]]*$');
3401
    }
3402
3403
    /**
3404
     * Returns true if the string is serialized, false otherwise.
3405
     *
3406
     * @param string $str
3407
     *
3408
     * @return bool whether or not $str is serialized
3409
     */
3410 7
    public static function is_serialized(string $str): bool
3411
    {
3412 7
        if ($str === '') {
3413 1
            return false;
3414
        }
3415
3416
        /** @noinspection PhpUsageOfSilenceOperatorInspection */
3417
        /** @noinspection UnserializeExploitsInspection */
3418 6
        return $str === 'b:0;'
3419
               ||
3420 6
               @\unserialize($str) !== false;
3421
    }
3422
3423
    /**
3424
     * Returns true if the string contains only lower case chars, false
3425
     * otherwise.
3426
     *
3427
     * @param string $str <p>The input string.</p>
3428
     *
3429
     * @return bool
3430
     *              <p>Whether or not $str contains only lower case characters.</p>
3431
     */
3432 8
    public static function is_uppercase(string $str): bool
3433
    {
3434 8
        if (self::$SUPPORT['mbstring'] === true) {
3435
            /** @noinspection PhpComposerExtensionStubsInspection */
3436 8
            return \mb_ereg_match('^[[:upper:]]*$', $str);
3437
        }
3438
3439
        return self::str_matches_pattern($str, '^[[:upper:]]*$');
3440
    }
3441
3442
    /**
3443
     * Check if the string is UTF-16.
3444
     *
3445
     * @param mixed $str                       <p>The input string.</p>
3446
     * @param bool  $check_if_string_is_binary
3447
     *
3448
     * @return false|int
3449
     *                   <strong>false</strong> if is't not UTF-16,<br>
3450
     *                   <strong>1</strong> for UTF-16LE,<br>
3451
     *                   <strong>2</strong> for UTF-16BE
3452
     */
3453 22
    public static function is_utf16($str, $check_if_string_is_binary = true)
3454
    {
3455
        // init
3456 22
        $str = (string) $str;
3457 22
        $str_chars = [];
3458
3459
        if (
3460 22
            $check_if_string_is_binary === true
3461
            &&
3462 22
            self::is_binary($str, true) === false
3463
        ) {
3464 2
            return false;
3465
        }
3466
3467 22
        if (self::$SUPPORT['mbstring'] === false) {
3468 3
            \trigger_error('UTF8::is_utf16() without mbstring may did not work correctly', \E_USER_WARNING);
3469
        }
3470
3471 22
        $str = self::remove_bom($str);
3472
3473 22
        $maybe_utf16le = 0;
3474 22
        $test = \mb_convert_encoding($str, 'UTF-8', 'UTF-16LE');
3475 22
        if ($test) {
3476 15
            $test2 = \mb_convert_encoding($test, 'UTF-16LE', 'UTF-8');
3477 15
            $test3 = \mb_convert_encoding($test2, 'UTF-8', 'UTF-16LE');
3478 15
            if ($test3 === $test) {
3479 15
                if ($str_chars === []) {
3480 15
                    $str_chars = self::count_chars($str, true, false);
3481
                }
3482 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...
3483 15
                    if (\in_array($test3char, $str_chars, true) === true) {
3484 15
                        ++$maybe_utf16le;
3485
                    }
3486
                }
3487 15
                unset($test3charEmpty);
3488
            }
3489
        }
3490
3491 22
        $maybe_utf16be = 0;
3492 22
        $test = \mb_convert_encoding($str, 'UTF-8', 'UTF-16BE');
3493 22
        if ($test) {
3494 15
            $test2 = \mb_convert_encoding($test, 'UTF-16BE', 'UTF-8');
3495 15
            $test3 = \mb_convert_encoding($test2, 'UTF-8', 'UTF-16BE');
3496 15
            if ($test3 === $test) {
3497 15
                if ($str_chars === []) {
3498 7
                    $str_chars = self::count_chars($str, true, false);
3499
                }
3500 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...
3501 15
                    if (\in_array($test3char, $str_chars, true) === true) {
3502 15
                        ++$maybe_utf16be;
3503
                    }
3504
                }
3505 15
                unset($test3charEmpty);
3506
            }
3507
        }
3508
3509 22
        if ($maybe_utf16be !== $maybe_utf16le) {
3510 7
            if ($maybe_utf16le > $maybe_utf16be) {
3511 5
                return 1;
3512
            }
3513
3514 6
            return 2;
3515
        }
3516
3517 18
        return false;
3518
    }
3519
3520
    /**
3521
     * Check if the string is UTF-32.
3522
     *
3523
     * @param mixed $str                       <p>The input string.</p>
3524
     * @param bool  $check_if_string_is_binary
3525
     *
3526
     * @return false|int
3527
     *                   <strong>false</strong> if is't not UTF-32,<br>
3528
     *                   <strong>1</strong> for UTF-32LE,<br>
3529
     *                   <strong>2</strong> for UTF-32BE
3530
     */
3531 20
    public static function is_utf32($str, $check_if_string_is_binary = true)
3532
    {
3533
        // init
3534 20
        $str = (string) $str;
3535 20
        $str_chars = [];
3536
3537
        if (
3538 20
            $check_if_string_is_binary === true
3539
            &&
3540 20
            self::is_binary($str, true) === false
3541
        ) {
3542 2
            return false;
3543
        }
3544
3545 20
        if (self::$SUPPORT['mbstring'] === false) {
3546 3
            \trigger_error('UTF8::is_utf32() without mbstring may did not work correctly', \E_USER_WARNING);
3547
        }
3548
3549 20
        $str = self::remove_bom($str);
3550
3551 20
        $maybe_utf32le = 0;
3552 20
        $test = \mb_convert_encoding($str, 'UTF-8', 'UTF-32LE');
3553 20
        if ($test) {
3554 13
            $test2 = \mb_convert_encoding($test, 'UTF-32LE', 'UTF-8');
3555 13
            $test3 = \mb_convert_encoding($test2, 'UTF-8', 'UTF-32LE');
3556 13
            if ($test3 === $test) {
3557 13
                if ($str_chars === []) {
3558 13
                    $str_chars = self::count_chars($str, true, false);
3559
                }
3560 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...
3561 13
                    if (\in_array($test3char, $str_chars, true) === true) {
3562 13
                        ++$maybe_utf32le;
3563
                    }
3564
                }
3565 13
                unset($test3charEmpty);
3566
            }
3567
        }
3568
3569 20
        $maybe_utf32be = 0;
3570 20
        $test = \mb_convert_encoding($str, 'UTF-8', 'UTF-32BE');
3571 20
        if ($test) {
3572 13
            $test2 = \mb_convert_encoding($test, 'UTF-32BE', 'UTF-8');
3573 13
            $test3 = \mb_convert_encoding($test2, 'UTF-8', 'UTF-32BE');
3574 13
            if ($test3 === $test) {
3575 13
                if ($str_chars === []) {
3576 7
                    $str_chars = self::count_chars($str, true, false);
3577
                }
3578 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...
3579 13
                    if (\in_array($test3char, $str_chars, true) === true) {
3580 13
                        ++$maybe_utf32be;
3581
                    }
3582
                }
3583 13
                unset($test3charEmpty);
3584
            }
3585
        }
3586
3587 20
        if ($maybe_utf32be !== $maybe_utf32le) {
3588 3
            if ($maybe_utf32le > $maybe_utf32be) {
3589 2
                return 1;
3590
            }
3591
3592 3
            return 2;
3593
        }
3594
3595 20
        return false;
3596
    }
3597
3598
    /**
3599
     * Checks whether the passed input contains only byte sequences that appear valid UTF-8.
3600
     *
3601
     * @param int|string|string[]|null $str    <p>The input to be checked.</p>
3602
     * @param bool                     $strict <p>Check also if the string is not UTF-16 or UTF-32.</p>
3603
     *
3604
     * @return bool
3605
     */
3606 82
    public static function is_utf8($str, bool $strict = false): bool
3607
    {
3608 82
        if (\is_array($str) === true) {
3609 2
            foreach ($str as &$v) {
3610 2
                if (self::is_utf8($v, $strict) === false) {
3611 2
                    return false;
3612
                }
3613
            }
3614
3615
            return true;
3616
        }
3617
3618 82
        return self::is_utf8_string((string) $str, $strict);
3619
    }
3620
3621
    /**
3622
     * (PHP 5 &gt;= 5.2.0, PECL json &gt;= 1.2.0)<br/>
3623
     * Decodes a JSON string
3624
     *
3625
     * @see http://php.net/manual/en/function.json-decode.php
3626
     *
3627
     * @param string $json    <p>
3628
     *                        The <i>json</i> string being decoded.
3629
     *                        </p>
3630
     *                        <p>
3631
     *                        This function only works with UTF-8 encoded strings.
3632
     *                        </p>
3633
     *                        <p>PHP implements a superset of
3634
     *                        JSON - it will also encode and decode scalar types and <b>NULL</b>. The JSON standard
3635
     *                        only supports these values when they are nested inside an array or an object.
3636
     *                        </p>
3637
     * @param bool   $assoc   [optional] <p>
3638
     *                        When <b>TRUE</b>, returned objects will be converted into
3639
     *                        associative arrays.
3640
     *                        </p>
3641
     * @param int    $depth   [optional] <p>
3642
     *                        User specified recursion depth.
3643
     *                        </p>
3644
     * @param int    $options [optional] <p>
3645
     *                        Bitmask of JSON decode options. Currently only
3646
     *                        <b>JSON_BIGINT_AS_STRING</b>
3647
     *                        is supported (default is to cast large integers as floats)
3648
     *                        </p>
3649
     *
3650
     * @return mixed
3651
     *               The value encoded in <i>json</i> in appropriate PHP type. Values true, false and
3652
     *               null (case-insensitive) are returned as <b>TRUE</b>, <b>FALSE</b> and <b>NULL</b> respectively.
3653
     *               <b>NULL</b> is returned if the <i>json</i> cannot be decoded or if the encoded data
3654
     *               is deeper than the recursion limit.
3655
     */
3656 43
    public static function json_decode(
3657
        string $json,
3658
        bool $assoc = false,
3659
        int $depth = 512,
3660
        int $options = 0
3661
    ) {
3662 43
        $json = self::filter($json);
3663
3664 43
        if (self::$SUPPORT['json'] === false) {
3665
            throw new \RuntimeException('ext-json: is not installed');
3666
        }
3667
3668
        /** @noinspection PhpComposerExtensionStubsInspection */
3669 43
        return \json_decode($json, $assoc, $depth, $options);
3670
    }
3671
3672
    /**
3673
     * (PHP 5 &gt;= 5.2.0, PECL json &gt;= 1.2.0)<br/>
3674
     * Returns the JSON representation of a value.
3675
     *
3676
     * @see http://php.net/manual/en/function.json-encode.php
3677
     *
3678
     * @param mixed $value   <p>
3679
     *                       The <i>value</i> being encoded. Can be any type except
3680
     *                       a resource.
3681
     *                       </p>
3682
     *                       <p>
3683
     *                       All string data must be UTF-8 encoded.
3684
     *                       </p>
3685
     *                       <p>PHP implements a superset of
3686
     *                       JSON - it will also encode and decode scalar types and <b>NULL</b>. The JSON standard
3687
     *                       only supports these values when they are nested inside an array or an object.
3688
     *                       </p>
3689
     * @param int   $options [optional] <p>
3690
     *                       Bitmask consisting of <b>JSON_HEX_QUOT</b>,
3691
     *                       <b>JSON_HEX_TAG</b>,
3692
     *                       <b>JSON_HEX_AMP</b>,
3693
     *                       <b>JSON_HEX_APOS</b>,
3694
     *                       <b>JSON_NUMERIC_CHECK</b>,
3695
     *                       <b>JSON_PRETTY_PRINT</b>,
3696
     *                       <b>JSON_UNESCAPED_SLASHES</b>,
3697
     *                       <b>JSON_FORCE_OBJECT</b>,
3698
     *                       <b>JSON_UNESCAPED_UNICODE</b>. The behaviour of these
3699
     *                       constants is described on
3700
     *                       the JSON constants page.
3701
     *                       </p>
3702
     * @param int   $depth   [optional] <p>
3703
     *                       Set the maximum depth. Must be greater than zero.
3704
     *                       </p>
3705
     *
3706
     * @return false|string
3707
     *                      A JSON encoded <strong>string</strong> on success or<br>
3708
     *                      <strong>FALSE</strong> on failure
3709
     */
3710 5
    public static function json_encode($value, int $options = 0, int $depth = 512)
3711
    {
3712 5
        $value = self::filter($value);
3713
3714 5
        if (self::$SUPPORT['json'] === false) {
3715
            throw new \RuntimeException('ext-json: is not installed');
3716
        }
3717
3718
        /** @noinspection PhpComposerExtensionStubsInspection */
3719 5
        return \json_encode($value, $options, $depth);
3720
    }
3721
3722
    /**
3723
     * Checks whether JSON is available on the server.
3724
     *
3725
     * @return bool
3726
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
3727
     */
3728
    public static function json_loaded(): bool
3729
    {
3730
        return \function_exists('json_decode');
3731
    }
3732
3733
    /**
3734
     * Makes string's first char lowercase.
3735
     *
3736
     * @param string      $str                           <p>The input string</p>
3737
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
3738
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
3739
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
3740
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
3741
     *
3742
     * @return string the resulting string
3743
     */
3744 46
    public static function lcfirst(
3745
        string $str,
3746
        string $encoding = 'UTF-8',
3747
        bool $clean_utf8 = false,
3748
        string $lang = null,
3749
        bool $try_to_keep_the_string_length = false
3750
    ): string {
3751 46
        if ($clean_utf8 === true) {
3752
            $str = self::clean($str);
3753
        }
3754
3755 46
        $use_mb_functions = ($lang === null && $try_to_keep_the_string_length === false);
3756
3757 46
        if ($encoding === 'UTF-8') {
3758 43
            $str_part_two = (string) \mb_substr($str, 1);
3759
3760 43
            if ($use_mb_functions === true) {
3761 43
                $str_part_one = \mb_strtolower(
3762 43
                    (string) \mb_substr($str, 0, 1)
3763
                );
3764
            } else {
3765
                $str_part_one = self::strtolower(
3766
                    (string) \mb_substr($str, 0, 1),
3767
                    $encoding,
3768
                    false,
3769
                    $lang,
3770 43
                    $try_to_keep_the_string_length
3771
                );
3772
            }
3773
        } else {
3774 3
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
3775
3776 3
            $str_part_two = (string) self::substr($str, 1, null, $encoding);
3777
3778 3
            $str_part_one = self::strtolower(
3779 3
                (string) self::substr($str, 0, 1, $encoding),
3780 3
                $encoding,
3781 3
                false,
3782 3
                $lang,
3783 3
                $try_to_keep_the_string_length
3784
            );
3785
        }
3786
3787 46
        return $str_part_one . $str_part_two;
3788
    }
3789
3790
    /**
3791
     * alias for "UTF8::lcfirst()"
3792
     *
3793
     * @param string      $str
3794
     * @param string      $encoding
3795
     * @param bool        $clean_utf8
3796
     * @param string|null $lang
3797
     * @param bool        $try_to_keep_the_string_length
3798
     *
3799
     * @return string
3800
     *
3801
     * @see UTF8::lcfirst()
3802
     * @deprecated <p>please use "UTF8::lcfirst()"</p>
3803
     */
3804 2
    public static function lcword(
3805
        string $str,
3806
        string $encoding = 'UTF-8',
3807
        bool $clean_utf8 = false,
3808
        string $lang = null,
3809
        bool $try_to_keep_the_string_length = false
3810
    ): string {
3811 2
        return self::lcfirst(
3812 2
            $str,
3813 2
            $encoding,
3814 2
            $clean_utf8,
3815 2
            $lang,
3816 2
            $try_to_keep_the_string_length
3817
        );
3818
    }
3819
3820
    /**
3821
     * Lowercase for all words in the string.
3822
     *
3823
     * @param string      $str                           <p>The input string.</p>
3824
     * @param string[]    $exceptions                    [optional] <p>Exclusion for some words.</p>
3825
     * @param string      $char_list                     [optional] <p>Additional chars that contains to words and do not start
3826
     *                                                   a new word.</p>
3827
     * @param string      $encoding                      [optional] <p>Set the charset.</p>
3828
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
3829
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
3830
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
3831
     *
3832
     * @return string
3833
     */
3834 2
    public static function lcwords(
3835
        string $str,
3836
        array $exceptions = [],
3837
        string $char_list = '',
3838
        string $encoding = 'UTF-8',
3839
        bool $clean_utf8 = false,
3840
        string $lang = null,
3841
        bool $try_to_keep_the_string_length = false
3842
    ): string {
3843 2
        if (!$str) {
3844 2
            return '';
3845
        }
3846
3847 2
        $words = self::str_to_words($str, $char_list);
3848 2
        $use_exceptions = $exceptions !== [];
3849
3850 2
        $words_str = '';
3851 2
        foreach ($words as &$word) {
3852 2
            if (!$word) {
3853 2
                continue;
3854
            }
3855
3856
            if (
3857 2
                $use_exceptions === false
3858
                ||
3859 2
                !\in_array($word, $exceptions, true)
3860
            ) {
3861 2
                $words_str .= self::lcfirst($word, $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
3862
            } else {
3863 2
                $words_str .= $word;
3864
            }
3865
        }
3866
3867 2
        return $words_str;
3868
    }
3869
3870
    /**
3871
     * alias for "UTF8::lcfirst()"
3872
     *
3873
     * @param string      $str
3874
     * @param string      $encoding
3875
     * @param bool        $clean_utf8
3876
     * @param string|null $lang
3877
     * @param bool        $try_to_keep_the_string_length
3878
     *
3879
     * @return string
3880
     *
3881
     * @see UTF8::lcfirst()
3882
     * @deprecated <p>please use "UTF8::lcfirst()"</p>
3883
     */
3884 5
    public static function lowerCaseFirst(
3885
        string $str,
3886
        string $encoding = 'UTF-8',
3887
        bool $clean_utf8 = false,
3888
        string $lang = null,
3889
        bool $try_to_keep_the_string_length = false
3890
    ): string {
3891 5
        return self::lcfirst(
3892 5
            $str,
3893 5
            $encoding,
3894 5
            $clean_utf8,
3895 5
            $lang,
3896 5
            $try_to_keep_the_string_length
3897
        );
3898
    }
3899
3900
    /**
3901
     * Strip whitespace or other characters from the beginning of a UTF-8 string.
3902
     *
3903
     * @param string      $str   <p>The string to be trimmed</p>
3904
     * @param string|null $chars <p>Optional characters to be stripped</p>
3905
     *
3906
     * @return string the string with unwanted characters stripped from the left
3907
     */
3908 22
    public static function ltrim(string $str = '', string $chars = null): string
3909
    {
3910 22
        if ($str === '') {
3911 3
            return '';
3912
        }
3913
3914 21
        if (self::$SUPPORT['mbstring'] === true) {
3915 21
            if ($chars) {
3916
                /** @noinspection PregQuoteUsageInspection */
3917 10
                $chars = \preg_quote($chars);
3918 10
                $pattern = "^[${chars}]+";
3919
            } else {
3920 14
                $pattern = '^[\\s]+';
3921
            }
3922
3923
            /** @noinspection PhpComposerExtensionStubsInspection */
3924 21
            return (string) \mb_ereg_replace($pattern, '', $str);
3925
        }
3926
3927
        if ($chars) {
3928
            $chars = \preg_quote($chars, '/');
3929
            $pattern = "^[${chars}]+";
3930
        } else {
3931
            $pattern = '^[\\s]+';
3932
        }
3933
3934
        return self::regex_replace($str, $pattern, '', '', '/');
3935
    }
3936
3937
    /**
3938
     * Returns the UTF-8 character with the maximum code point in the given data.
3939
     *
3940
     * @param array<string>|string $arg <p>A UTF-8 encoded string or an array of such strings.</p>
3941
     *
3942
     * @return string|null the character with the highest code point than others, returns null on failure or empty input
3943
     */
3944
    public static function max($arg)
3945
    {
3946 2
        if (\is_array($arg) === true) {
3947 2
            $arg = \implode('', $arg);
3948
        }
3949
3950 2
        $codepoints = self::codepoints($arg, false);
3951 2
        if ($codepoints === []) {
3952 2
            return null;
3953
        }
3954
3955 2
        $codepoint_max = \max($codepoints);
3956
3957 2
        return self::chr($codepoint_max);
3958
    }
3959
3960
    /**
3961
     * Calculates and returns the maximum number of bytes taken by any
3962
     * UTF-8 encoded character in the given string.
3963
     *
3964
     * @param string $str <p>The original Unicode string.</p>
3965
     *
3966
     * @return int max byte lengths of the given chars
3967
     */
3968
    public static function max_chr_width(string $str): int
3969
    {
3970 2
        $bytes = self::chr_size_list($str);
3971 2
        if ($bytes !== []) {
3972 2
            return (int) \max($bytes);
3973
        }
3974
3975 2
        return 0;
3976
    }
3977
3978
    /**
3979
     * Checks whether mbstring is available on the server.
3980
     *
3981
     * @return bool
3982
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
3983
     */
3984
    public static function mbstring_loaded(): bool
3985
    {
3986 26
        return \extension_loaded('mbstring');
3987
    }
3988
3989
    /**
3990
     * Returns the UTF-8 character with the minimum code point in the given data.
3991
     *
3992
     * @param mixed $arg <strong>A UTF-8 encoded string or an array of such strings.</strong>
3993
     *
3994
     * @return string|null the character with the lowest code point than others, returns null on failure or empty input
3995
     */
3996
    public static function min($arg)
3997
    {
3998 2
        if (\is_array($arg) === true) {
3999 2
            $arg = \implode('', $arg);
4000
        }
4001
4002 2
        $codepoints = self::codepoints($arg, false);
4003 2
        if ($codepoints === []) {
4004 2
            return null;
4005
        }
4006
4007 2
        $codepoint_min = \min($codepoints);
4008
4009 2
        return self::chr($codepoint_min);
4010
    }
4011
4012
    /**
4013
     * alias for "UTF8::normalize_encoding()"
4014
     *
4015
     * @param mixed $encoding
4016
     * @param mixed $fallback
4017
     *
4018
     * @return mixed
4019
     *
4020
     * @see UTF8::normalize_encoding()
4021
     * @deprecated <p>please use "UTF8::normalize_encoding()"</p>
4022
     */
4023
    public static function normalizeEncoding($encoding, $fallback = '')
4024
    {
4025 2
        return self::normalize_encoding($encoding, $fallback);
4026
    }
4027
4028
    /**
4029
     * Normalize the encoding-"name" input.
4030
     *
4031
     * @param mixed $encoding <p>e.g.: ISO, UTF8, WINDOWS-1251 etc.</p>
4032
     * @param mixed $fallback <p>e.g.: UTF-8</p>
4033
     *
4034
     * @return mixed e.g.: ISO-8859-1, UTF-8, WINDOWS-1251 etc.<br>Will return a empty string as fallback (by default)
4035
     */
4036
    public static function normalize_encoding($encoding, $fallback = '')
4037
    {
4038 331
        static $STATIC_NORMALIZE_ENCODING_CACHE = [];
4039
4040
        // init
4041 331
        $encoding = (string) $encoding;
4042
4043 331
        if (!$encoding) {
4044 285
            return $fallback;
4045
        }
4046
4047
        if (
4048 51
            $encoding === 'UTF-8'
4049
            ||
4050 51
            $encoding === 'UTF8'
4051
        ) {
4052 28
            return 'UTF-8';
4053
        }
4054
4055
        if (
4056 43
            $encoding === '8BIT'
4057
            ||
4058 43
            $encoding === 'BINARY'
4059
        ) {
4060
            return 'CP850';
4061
        }
4062
4063
        if (
4064 43
            $encoding === 'HTML'
4065
            ||
4066 43
            $encoding === 'HTML-ENTITIES'
4067
        ) {
4068 2
            return 'HTML-ENTITIES';
4069
        }
4070
4071
        if (
4072 43
            $encoding === 'ISO'
4073
            ||
4074 43
            $encoding === 'ISO-8859-1'
4075
        ) {
4076 39
            return 'ISO-8859-1';
4077
        }
4078
4079
        if (
4080 12
            $encoding === '1' // only a fallback, for non "strict_types" usage ...
4081
            ||
4082 12
            $encoding === '0' // only a fallback, for non "strict_types" usage ...
4083
        ) {
4084 1
            return $fallback;
4085
        }
4086
4087 11
        if (isset($STATIC_NORMALIZE_ENCODING_CACHE[$encoding])) {
4088 8
            return $STATIC_NORMALIZE_ENCODING_CACHE[$encoding];
4089
        }
4090
4091 5
        if (self::$ENCODINGS === null) {
4092 1
            self::$ENCODINGS = self::getData('encodings');
4093
        }
4094
4095 5
        if (\in_array($encoding, self::$ENCODINGS, true)) {
4096 3
            $STATIC_NORMALIZE_ENCODING_CACHE[$encoding] = $encoding;
4097
4098 3
            return $encoding;
4099
        }
4100
4101 4
        $encoding_original = $encoding;
4102 4
        $encoding = \strtoupper($encoding);
4103 4
        $encoding_upper_helper = (string) \preg_replace('/[^a-zA-Z0-9]/u', '', $encoding);
4104
4105
        $equivalences = [
4106 4
            'ISO8859'     => 'ISO-8859-1',
4107
            'ISO88591'    => 'ISO-8859-1',
4108
            'ISO'         => 'ISO-8859-1',
4109
            'LATIN'       => 'ISO-8859-1',
4110
            'LATIN1'      => 'ISO-8859-1', // Western European
4111
            'ISO88592'    => 'ISO-8859-2',
4112
            'LATIN2'      => 'ISO-8859-2', // Central European
4113
            'ISO88593'    => 'ISO-8859-3',
4114
            'LATIN3'      => 'ISO-8859-3', // Southern European
4115
            'ISO88594'    => 'ISO-8859-4',
4116
            'LATIN4'      => 'ISO-8859-4', // Northern European
4117
            'ISO88595'    => 'ISO-8859-5',
4118
            'ISO88596'    => 'ISO-8859-6', // Greek
4119
            'ISO88597'    => 'ISO-8859-7',
4120
            'ISO88598'    => 'ISO-8859-8', // Hebrew
4121
            'ISO88599'    => 'ISO-8859-9',
4122
            'LATIN5'      => 'ISO-8859-9', // Turkish
4123
            'ISO885911'   => 'ISO-8859-11',
4124
            'TIS620'      => 'ISO-8859-11', // Thai
4125
            'ISO885910'   => 'ISO-8859-10',
4126
            'LATIN6'      => 'ISO-8859-10', // Nordic
4127
            'ISO885913'   => 'ISO-8859-13',
4128
            'LATIN7'      => 'ISO-8859-13', // Baltic
4129
            'ISO885914'   => 'ISO-8859-14',
4130
            'LATIN8'      => 'ISO-8859-14', // Celtic
4131
            'ISO885915'   => 'ISO-8859-15',
4132
            'LATIN9'      => 'ISO-8859-15', // Western European (with some extra chars e.g. €)
4133
            'ISO885916'   => 'ISO-8859-16',
4134
            'LATIN10'     => 'ISO-8859-16', // Southeast European
4135
            'CP1250'      => 'WINDOWS-1250',
4136
            'WIN1250'     => 'WINDOWS-1250',
4137
            'WINDOWS1250' => 'WINDOWS-1250',
4138
            'CP1251'      => 'WINDOWS-1251',
4139
            'WIN1251'     => 'WINDOWS-1251',
4140
            'WINDOWS1251' => 'WINDOWS-1251',
4141
            'CP1252'      => 'WINDOWS-1252',
4142
            'WIN1252'     => 'WINDOWS-1252',
4143
            'WINDOWS1252' => 'WINDOWS-1252',
4144
            'CP1253'      => 'WINDOWS-1253',
4145
            'WIN1253'     => 'WINDOWS-1253',
4146
            'WINDOWS1253' => 'WINDOWS-1253',
4147
            'CP1254'      => 'WINDOWS-1254',
4148
            'WIN1254'     => 'WINDOWS-1254',
4149
            'WINDOWS1254' => 'WINDOWS-1254',
4150
            'CP1255'      => 'WINDOWS-1255',
4151
            'WIN1255'     => 'WINDOWS-1255',
4152
            'WINDOWS1255' => 'WINDOWS-1255',
4153
            'CP1256'      => 'WINDOWS-1256',
4154
            'WIN1256'     => 'WINDOWS-1256',
4155
            'WINDOWS1256' => 'WINDOWS-1256',
4156
            'CP1257'      => 'WINDOWS-1257',
4157
            'WIN1257'     => 'WINDOWS-1257',
4158
            'WINDOWS1257' => 'WINDOWS-1257',
4159
            'CP1258'      => 'WINDOWS-1258',
4160
            'WIN1258'     => 'WINDOWS-1258',
4161
            'WINDOWS1258' => 'WINDOWS-1258',
4162
            'UTF16'       => 'UTF-16',
4163
            'UTF32'       => 'UTF-32',
4164
            'UTF8'        => 'UTF-8',
4165
            'UTF'         => 'UTF-8',
4166
            'UTF7'        => 'UTF-7',
4167
            '8BIT'        => 'CP850',
4168
            'BINARY'      => 'CP850',
4169
        ];
4170
4171 4
        if (!empty($equivalences[$encoding_upper_helper])) {
4172 3
            $encoding = $equivalences[$encoding_upper_helper];
4173
        }
4174
4175 4
        $STATIC_NORMALIZE_ENCODING_CACHE[$encoding_original] = $encoding;
4176
4177 4
        return $encoding;
4178
    }
4179
4180
    /**
4181
     * Standardize line ending to unix-like.
4182
     *
4183
     * @param string $str
4184
     *
4185
     * @return string
4186
     */
4187
    public static function normalize_line_ending(string $str): string
4188
    {
4189 5
        return \str_replace(["\r\n", "\r"], "\n", $str);
4190
    }
4191
4192
    /**
4193
     * Normalize some MS Word special characters.
4194
     *
4195
     * @param string $str <p>The string to be normalized.</p>
4196
     *
4197
     * @return string
4198
     */
4199
    public static function normalize_msword(string $str): string
4200
    {
4201 10
        return ASCII::normalize_msword($str);
4202
    }
4203
4204
    /**
4205
     * Normalize the whitespace.
4206
     *
4207
     * @param string $str                        <p>The string to be normalized.</p>
4208
     * @param bool   $keep_non_breaking_space    [optional] <p>Set to true, to keep non-breaking-spaces.</p>
4209
     * @param bool   $keep_bidi_unicode_controls [optional] <p>Set to true, to keep non-printable (for the web)
4210
     *                                           bidirectional text chars.</p>
4211
     *
4212
     * @return string
4213
     */
4214
    public static function normalize_whitespace(
4215
        string $str,
4216
        bool $keep_non_breaking_space = false,
4217
        bool $keep_bidi_unicode_controls = false
4218
    ): string {
4219 61
        return ASCII::normalize_whitespace(
4220 61
            $str,
4221 61
            $keep_non_breaking_space,
4222 61
            $keep_bidi_unicode_controls
4223
        );
4224
    }
4225
4226
    /**
4227
     * Calculates Unicode code point of the given UTF-8 encoded character.
4228
     *
4229
     * INFO: opposite to UTF8::chr()
4230
     *
4231
     * @param string $chr      <p>The character of which to calculate code point.<p/>
4232
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
4233
     *
4234
     * @return int
4235
     *             Unicode code point of the given character,<br>
4236
     *             0 on invalid UTF-8 byte sequence
4237
     */
4238
    public static function ord($chr, string $encoding = 'UTF-8'): int
4239
    {
4240 26
        static $CHAR_CACHE = [];
4241
4242
        // init
4243 26
        $chr = (string) $chr;
4244
4245 26
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
4246 5
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
4247
        }
4248
4249 26
        $cache_key = $chr . $encoding;
4250 26
        if (isset($CHAR_CACHE[$cache_key]) === true) {
4251 26
            return $CHAR_CACHE[$cache_key];
4252
        }
4253
4254
        // check again, if it's still not UTF-8
4255 10
        if ($encoding !== 'UTF-8') {
4256 3
            $chr = self::encode($encoding, $chr);
4257
        }
4258
4259 10
        if (self::$ORD === null) {
4260
            self::$ORD = self::getData('ord');
4261
        }
4262
4263 10
        if (isset(self::$ORD[$chr])) {
4264 10
            return $CHAR_CACHE[$cache_key] = self::$ORD[$chr];
4265
        }
4266
4267
        //
4268
        // fallback via "IntlChar"
4269
        //
4270
4271 6
        if (self::$SUPPORT['intlChar'] === true) {
4272
            /** @noinspection PhpComposerExtensionStubsInspection */
4273 5
            $code = \IntlChar::ord($chr);
4274 5
            if ($code) {
4275 5
                return $CHAR_CACHE[$cache_key] = $code;
4276
            }
4277
        }
4278
4279
        //
4280
        // fallback via vanilla php
4281
        //
4282
4283
        /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
4284 1
        $chr = \unpack('C*', (string) \substr($chr, 0, 4));
4285
        /** @noinspection OffsetOperationsInspection */
4286 1
        $code = $chr ? $chr[1] : 0;
4287
4288
        /** @noinspection OffsetOperationsInspection */
4289 1
        if ($code >= 0xF0 && isset($chr[4])) {
4290
            /** @noinspection UnnecessaryCastingInspection */
4291
            /** @noinspection OffsetOperationsInspection */
4292
            return $CHAR_CACHE[$cache_key] = (int) ((($code - 0xF0) << 18) + (($chr[2] - 0x80) << 12) + (($chr[3] - 0x80) << 6) + $chr[4] - 0x80);
4293
        }
4294
4295
        /** @noinspection OffsetOperationsInspection */
4296 1
        if ($code >= 0xE0 && isset($chr[3])) {
4297
            /** @noinspection UnnecessaryCastingInspection */
4298
            /** @noinspection OffsetOperationsInspection */
4299 1
            return $CHAR_CACHE[$cache_key] = (int) ((($code - 0xE0) << 12) + (($chr[2] - 0x80) << 6) + $chr[3] - 0x80);
4300
        }
4301
4302
        /** @noinspection OffsetOperationsInspection */
4303 1
        if ($code >= 0xC0 && isset($chr[2])) {
4304
            /** @noinspection UnnecessaryCastingInspection */
4305
            /** @noinspection OffsetOperationsInspection */
4306 1
            return $CHAR_CACHE[$cache_key] = (int) ((($code - 0xC0) << 6) + $chr[2] - 0x80);
4307
        }
4308
4309
        return $CHAR_CACHE[$cache_key] = $code;
4310
    }
4311
4312
    /**
4313
     * Parses the string into an array (into the the second parameter).
4314
     *
4315
     * WARNING: Unlike "parse_str()", this method does not (re-)place variables in the current scope,
4316
     *          if the second parameter is not set!
4317
     *
4318
     * @see http://php.net/manual/en/function.parse-str.php
4319
     *
4320
     * @param string $str        <p>The input string.</p>
4321
     * @param array  $result     <p>The result will be returned into this reference parameter.</p>
4322
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
4323
     *
4324
     * @return bool
4325
     *              Will return <strong>false</strong> if php can't parse the string and we haven't any $result
4326
     */
4327
    public static function parse_str(string $str, &$result, bool $clean_utf8 = false): bool
4328
    {
4329 2
        if ($clean_utf8 === true) {
4330 2
            $str = self::clean($str);
4331
        }
4332
4333 2
        if (self::$SUPPORT['mbstring'] === true) {
4334 2
            $return = \mb_parse_str($str, $result);
4335
4336 2
            return $return !== false && $result !== [];
4337
        }
4338
4339
        /** @noinspection PhpVoidFunctionResultUsedInspection */
4340
        \parse_str($str, $result);
4341
4342
        return $result !== [];
4343
    }
4344
4345
    /**
4346
     * Checks if \u modifier is available that enables Unicode support in PCRE.
4347
     *
4348
     * @return bool
4349
     *              <strong>true</strong> if support is available,<br>
4350
     *              <strong>false</strong> otherwise
4351
     */
4352
    public static function pcre_utf8_support(): bool
4353
    {
4354
        /** @noinspection PhpUsageOfSilenceOperatorInspection */
4355 102
        return (bool) @\preg_match('//u', '');
4356
    }
4357
4358
    /**
4359
     * Create an array containing a range of UTF-8 characters.
4360
     *
4361
     * @param mixed     $var1      <p>Numeric or hexadecimal code points, or a UTF-8 character to start from.</p>
4362
     * @param mixed     $var2      <p>Numeric or hexadecimal code points, or a UTF-8 character to end at.</p>
4363
     * @param bool      $use_ctype <p>use ctype to detect numeric and hexadecimal, otherwise we will use a simple "is_numeric"</p>
4364
     * @param string    $encoding  [optional] <p>Set the charset for e.g. "mb_" function</p>
4365
     * @param float|int $step      [optional] <p>
4366
     *                             If a step value is given, it will be used as the
4367
     *                             increment between elements in the sequence. step
4368
     *                             should be given as a positive number. If not specified,
4369
     *                             step will default to 1.
4370
     *                             </p>
4371
     *
4372
     * @return string[]
4373
     */
4374
    public static function range(
4375
        $var1,
4376
        $var2,
4377
        bool $use_ctype = true,
4378
        string $encoding = 'UTF-8',
4379
        $step = 1
4380
    ): array {
4381 2
        if (!$var1 || !$var2) {
4382 2
            return [];
4383
        }
4384
4385 2
        if ($step !== 1) {
4386 1
            if (!\is_numeric($step)) {
0 ignored issues
show
introduced by
The condition is_numeric($step) is always true.
Loading history...
4387
                throw new \InvalidArgumentException('$step need to be a number, type given: ' . \gettype($step));
4388
            }
4389
4390 1
            if ($step <= 0) {
4391
                throw new \InvalidArgumentException('$step need to be a positive number, given: ' . $step);
4392
            }
4393
        }
4394
4395 2
        if ($use_ctype && self::$SUPPORT['ctype'] === false) {
4396
            throw new \RuntimeException('ext-ctype: is not installed');
4397
        }
4398
4399 2
        $is_digit = false;
4400 2
        $is_xdigit = false;
4401
4402
        /** @noinspection PhpComposerExtensionStubsInspection */
4403 2
        if ($use_ctype && \ctype_digit((string) $var1) && \ctype_digit((string) $var2)) {
4404 2
            $is_digit = true;
4405 2
            $start = (int) $var1;
4406 2
        } /** @noinspection PhpComposerExtensionStubsInspection */ elseif ($use_ctype && \ctype_xdigit($var1) && \ctype_xdigit($var2)) {
4407
            $is_xdigit = true;
4408
            $start = (int) self::hex_to_int($var1);
4409 2
        } elseif (!$use_ctype && \is_numeric($var1)) {
4410 1
            $start = (int) $var1;
4411
        } else {
4412 2
            $start = self::ord($var1);
4413
        }
4414
4415 2
        if (!$start) {
4416
            return [];
4417
        }
4418
4419 2
        if ($is_digit) {
4420 2
            $end = (int) $var2;
4421 2
        } elseif ($is_xdigit) {
4422
            $end = (int) self::hex_to_int($var2);
4423 2
        } elseif (!$use_ctype && \is_numeric($var2)) {
4424 1
            $end = (int) $var2;
4425
        } else {
4426 2
            $end = self::ord($var2);
4427
        }
4428
4429 2
        if (!$end) {
4430
            return [];
4431
        }
4432
4433 2
        $array = [];
4434 2
        foreach (\range($start, $end, $step) as $i) {
4435 2
            $array[] = (string) self::chr((int) $i, $encoding);
4436
        }
4437
4438 2
        return $array;
4439
    }
4440
4441
    /**
4442
     * Multi decode HTML entity + fix urlencoded-win1252-chars.
4443
     *
4444
     * e.g:
4445
     * 'test+test'                     => 'test+test'
4446
     * 'D&#252;sseldorf'               => 'Düsseldorf'
4447
     * 'D%FCsseldorf'                  => 'Düsseldorf'
4448
     * 'D&#xFC;sseldorf'               => 'Düsseldorf'
4449
     * 'D%26%23xFC%3Bsseldorf'         => 'Düsseldorf'
4450
     * 'Düsseldorf'                   => 'Düsseldorf'
4451
     * 'D%C3%BCsseldorf'               => 'Düsseldorf'
4452
     * 'D%C3%83%C2%BCsseldorf'         => 'Düsseldorf'
4453
     * 'D%25C3%2583%25C2%25BCsseldorf' => 'Düsseldorf'
4454
     *
4455
     * @param string $str          <p>The input string.</p>
4456
     * @param bool   $multi_decode <p>Decode as often as possible.</p>
4457
     *
4458
     * @return string
4459
     */
4460
    public static function rawurldecode(string $str, bool $multi_decode = true): string
4461
    {
4462 6
        if ($str === '') {
4463 4
            return '';
4464
        }
4465
4466
        if (
4467 6
            \strpos($str, '&') === false
4468
            &&
4469 6
            \strpos($str, '%') === false
4470
            &&
4471 6
            \strpos($str, '+') === false
4472
            &&
4473 6
            \strpos($str, '\u') === false
4474
        ) {
4475 4
            return self::fix_simple_utf8($str);
4476
        }
4477
4478 6
        $str = self::urldecode_unicode_helper($str);
4479
4480
        do {
4481 6
            $str_compare = $str;
4482
4483
            /**
4484
             * @psalm-suppress PossiblyInvalidArgument
4485
             */
4486 6
            $str = self::fix_simple_utf8(
4487 6
                \rawurldecode(
4488 6
                    self::html_entity_decode(
4489 6
                        self::to_utf8($str),
4490 6
                        \ENT_QUOTES | \ENT_HTML5
4491
                    )
4492
                )
4493
            );
4494 6
        } while ($multi_decode === true && $str_compare !== $str);
4495
4496 6
        return $str;
4497
    }
4498
4499
    /**
4500
     * Replaces all occurrences of $pattern in $str by $replacement.
4501
     *
4502
     * @param string $str         <p>The input string.</p>
4503
     * @param string $pattern     <p>The regular expression pattern.</p>
4504
     * @param string $replacement <p>The string to replace with.</p>
4505
     * @param string $options     [optional] <p>Matching conditions to be used.</p>
4506
     * @param string $delimiter   [optional] <p>Delimiter the the regex. Default: '/'</p>
4507
     *
4508
     * @return string
4509
     */
4510
    public static function regex_replace(
4511
        string $str,
4512
        string $pattern,
4513
        string $replacement,
4514
        string $options = '',
4515
        string $delimiter = '/'
4516
    ): string {
4517 18
        if ($options === 'msr') {
4518 9
            $options = 'ms';
4519
        }
4520
4521
        // fallback
4522 18
        if (!$delimiter) {
4523
            $delimiter = '/';
4524
        }
4525
4526 18
        return (string) \preg_replace(
4527 18
            $delimiter . $pattern . $delimiter . 'u' . $options,
4528 18
            $replacement,
4529 18
            $str
4530
        );
4531
    }
4532
4533
    /**
4534
     * alias for "UTF8::remove_bom()"
4535
     *
4536
     * @param string $str
4537
     *
4538
     * @return string
4539
     *
4540
     * @see UTF8::remove_bom()
4541
     * @deprecated <p>please use "UTF8::remove_bom()"</p>
4542
     */
4543
    public static function removeBOM(string $str): string
4544
    {
4545
        return self::remove_bom($str);
4546
    }
4547
4548
    /**
4549
     * Remove the BOM from UTF-8 / UTF-16 / UTF-32 strings.
4550
     *
4551
     * @param string $str <p>The input string.</p>
4552
     *
4553
     * @return string string without UTF-BOM
4554
     */
4555
    public static function remove_bom(string $str): string
4556
    {
4557 55
        if ($str === '') {
4558 9
            return '';
4559
        }
4560
4561 55
        $str_length = \strlen($str);
4562 55
        foreach (self::$BOM as $bom_string => $bom_byte_length) {
4563 55
            if (\strpos($str, $bom_string, 0) === 0) {
4564
                /** @var false|string $str_tmp - needed for PhpStan (stubs error) */
4565 11
                $str_tmp = \substr($str, $bom_byte_length, $str_length);
4566 11
                if ($str_tmp === false) {
4567
                    return '';
4568
                }
4569
4570 11
                $str_length -= (int) $bom_byte_length;
4571
4572 55
                $str = (string) $str_tmp;
4573
            }
4574
        }
4575
4576 55
        return $str;
4577
    }
4578
4579
    /**
4580
     * Removes duplicate occurrences of a string in another string.
4581
     *
4582
     * @param string          $str  <p>The base string.</p>
4583
     * @param string|string[] $what <p>String to search for in the base string.</p>
4584
     *
4585
     * @return string the result string with removed duplicates
4586
     */
4587
    public static function remove_duplicates(string $str, $what = ' '): string
4588
    {
4589 2
        if (\is_string($what) === true) {
4590 2
            $what = [$what];
4591
        }
4592
4593 2
        if (\is_array($what) === true) {
0 ignored issues
show
introduced by
The condition is_array($what) === true is always true.
Loading history...
4594
            /** @noinspection ForeachSourceInspection */
4595 2
            foreach ($what as $item) {
4596 2
                $str = (string) \preg_replace('/(' . \preg_quote($item, '/') . ')+/u', $item, $str);
4597
            }
4598
        }
4599
4600 2
        return $str;
4601
    }
4602
4603
    /**
4604
     * Remove html via "strip_tags()" from the string.
4605
     *
4606
     * @param string $str
4607
     * @param string $allowable_tags [optional] <p>You can use the optional second parameter to specify tags which should
4608
     *                               not be stripped. Default: null
4609
     *                               </p>
4610
     *
4611
     * @return string
4612
     */
4613
    public static function remove_html(string $str, string $allowable_tags = ''): string
4614
    {
4615 6
        return \strip_tags($str, $allowable_tags);
4616
    }
4617
4618
    /**
4619
     * Remove all breaks [<br> | \r\n | \r | \n | ...] from the string.
4620
     *
4621
     * @param string $str
4622
     * @param string $replacement [optional] <p>Default is a empty string.</p>
4623
     *
4624
     * @return string
4625
     */
4626
    public static function remove_html_breaks(string $str, string $replacement = ''): string
4627
    {
4628 6
        return (string) \preg_replace("#/\r\n|\r|\n|<br.*/?>#isU", $replacement, $str);
4629
    }
4630
4631
    /**
4632
     * Remove invisible characters from a string.
4633
     *
4634
     * e.g.: This prevents sandwiching null characters between ascii characters, like Java\0script.
4635
     *
4636
     * copy&past from https://github.com/bcit-ci/CodeIgniter/blob/develop/system/core/Common.php
4637
     *
4638
     * @param string $str
4639
     * @param bool   $url_encoded
4640
     * @param string $replacement
4641
     *
4642
     * @return string
4643
     */
4644
    public static function remove_invisible_characters(
4645
        string $str,
4646
        bool $url_encoded = true,
4647
        string $replacement = ''
4648
    ): string {
4649 89
        return ASCII::remove_invisible_characters(
4650 89
            $str,
4651 89
            $url_encoded,
4652 89
            $replacement
4653
        );
4654
    }
4655
4656
    /**
4657
     * Returns a new string with the prefix $substring removed, if present.
4658
     *
4659
     * @param string $str
4660
     * @param string $substring <p>The prefix to remove.</p>
4661
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
4662
     *
4663
     * @return string string without the prefix $substring
4664
     */
4665
    public static function remove_left(
4666
        string $str,
4667
        string $substring,
4668
        string $encoding = 'UTF-8'
4669
    ): string {
4670 12
        if ($substring && \strpos($str, $substring) === 0) {
4671 6
            if ($encoding === 'UTF-8') {
4672 4
                return (string) \mb_substr(
4673 4
                    $str,
4674 4
                    (int) \mb_strlen($substring)
4675
                );
4676
            }
4677
4678 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
4679
4680 2
            return (string) self::substr(
4681 2
                $str,
4682 2
                (int) self::strlen($substring, $encoding),
4683 2
                null,
4684 2
                $encoding
4685
            );
4686
        }
4687
4688 6
        return $str;
4689
    }
4690
4691
    /**
4692
     * Returns a new string with the suffix $substring removed, if present.
4693
     *
4694
     * @param string $str
4695
     * @param string $substring <p>The suffix to remove.</p>
4696
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
4697
     *
4698
     * @return string string having a $str without the suffix $substring
4699
     */
4700
    public static function remove_right(
4701
        string $str,
4702
        string $substring,
4703
        string $encoding = 'UTF-8'
4704
    ): string {
4705 12
        if ($substring && \substr($str, -\strlen($substring)) === $substring) {
4706 6
            if ($encoding === 'UTF-8') {
4707 4
                return (string) \mb_substr(
4708 4
                    $str,
4709 4
                    0,
4710 4
                    (int) \mb_strlen($str) - (int) \mb_strlen($substring)
4711
                );
4712
            }
4713
4714 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
4715
4716 2
            return (string) self::substr(
4717 2
                $str,
4718 2
                0,
4719 2
                (int) self::strlen($str, $encoding) - (int) self::strlen($substring, $encoding),
4720 2
                $encoding
4721
            );
4722
        }
4723
4724 6
        return $str;
4725
    }
4726
4727
    /**
4728
     * Replaces all occurrences of $search in $str by $replacement.
4729
     *
4730
     * @param string $str            <p>The input string.</p>
4731
     * @param string $search         <p>The needle to search for.</p>
4732
     * @param string $replacement    <p>The string to replace with.</p>
4733
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
4734
     *
4735
     * @return string string after the replacements
4736
     */
4737
    public static function replace(
4738
        string $str,
4739
        string $search,
4740
        string $replacement,
4741
        bool $case_sensitive = true
4742
    ): string {
4743 29
        if ($case_sensitive) {
4744 22
            return \str_replace($search, $replacement, $str);
4745
        }
4746
4747 7
        return self::str_ireplace($search, $replacement, $str);
4748
    }
4749
4750
    /**
4751
     * Replaces all occurrences of $search in $str by $replacement.
4752
     *
4753
     * @param string       $str            <p>The input string.</p>
4754
     * @param array        $search         <p>The elements to search for.</p>
4755
     * @param array|string $replacement    <p>The string to replace with.</p>
4756
     * @param bool         $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
4757
     *
4758
     * @return string string after the replacements
4759
     */
4760
    public static function replace_all(
4761
        string $str,
4762
        array $search,
4763
        $replacement,
4764
        bool $case_sensitive = true
4765
    ): string {
4766 30
        if ($case_sensitive) {
4767 23
            return \str_replace($search, $replacement, $str);
4768
        }
4769
4770 7
        return self::str_ireplace($search, $replacement, $str);
4771
    }
4772
4773
    /**
4774
     * Replace the diamond question mark (�) and invalid-UTF8 chars with the replacement.
4775
     *
4776
     * @param string $str                        <p>The input string</p>
4777
     * @param string $replacement_char           <p>The replacement character.</p>
4778
     * @param bool   $process_invalid_utf8_chars <p>Convert invalid UTF-8 chars </p>
4779
     *
4780
     * @return string
4781
     */
4782
    public static function replace_diamond_question_mark(
4783
        string $str,
4784
        string $replacement_char = '',
4785
        bool $process_invalid_utf8_chars = true
4786
    ): string {
4787 35
        if ($str === '') {
4788 9
            return '';
4789
        }
4790
4791 35
        if ($process_invalid_utf8_chars === true) {
4792 35
            $replacement_char_helper = $replacement_char;
4793 35
            if ($replacement_char === '') {
4794 35
                $replacement_char_helper = 'none';
4795
            }
4796
4797 35
            if (self::$SUPPORT['mbstring'] === false) {
4798
                // if there is no native support for "mbstring",
4799
                // then we need to clean the string before ...
4800
                $str = self::clean($str);
4801
            }
4802
4803 35
            $save = \mb_substitute_character();
4804 35
            \mb_substitute_character($replacement_char_helper);
4805
            // the polyfill maybe return false, so cast to string
4806 35
            $str = (string) \mb_convert_encoding($str, 'UTF-8', 'UTF-8');
4807 35
            \mb_substitute_character($save);
4808
        }
4809
4810 35
        return \str_replace(
4811
            [
4812 35
                "\xEF\xBF\xBD",
4813
                '�',
4814
            ],
4815
            [
4816 35
                $replacement_char,
4817 35
                $replacement_char,
4818
            ],
4819 35
            $str
4820
        );
4821
    }
4822
4823
    /**
4824
     * Strip whitespace or other characters from the end of a UTF-8 string.
4825
     *
4826
     * @param string      $str   <p>The string to be trimmed.</p>
4827
     * @param string|null $chars <p>Optional characters to be stripped.</p>
4828
     *
4829
     * @return string the string with unwanted characters stripped from the right
4830
     */
4831
    public static function rtrim(string $str = '', string $chars = null): string
4832
    {
4833 20
        if ($str === '') {
4834 3
            return '';
4835
        }
4836
4837 19
        if (self::$SUPPORT['mbstring'] === true) {
4838 19
            if ($chars) {
4839
                /** @noinspection PregQuoteUsageInspection */
4840 8
                $chars = \preg_quote($chars);
4841 8
                $pattern = "[${chars}]+$";
4842
            } else {
4843 14
                $pattern = '[\\s]+$';
4844
            }
4845
4846
            /** @noinspection PhpComposerExtensionStubsInspection */
4847 19
            return (string) \mb_ereg_replace($pattern, '', $str);
4848
        }
4849
4850
        if ($chars) {
4851
            $chars = \preg_quote($chars, '/');
4852
            $pattern = "[${chars}]+$";
4853
        } else {
4854
            $pattern = '[\\s]+$';
4855
        }
4856
4857
        return self::regex_replace($str, $pattern, '', '', '/');
4858
    }
4859
4860
    /**
4861
     * WARNING: Print native UTF-8 support (libs), e.g. for debugging.
4862
     *
4863
     * @psalm-suppress MissingReturnType
4864
     */
4865
    public static function showSupport()
4866
    {
4867 2
        echo '<pre>';
4868 2
        foreach (self::$SUPPORT as $key => &$value) {
4869 2
            echo $key . ' - ' . \print_r($value, true) . "\n<br>";
4870
        }
4871 2
        unset($value);
4872 2
        echo '</pre>';
4873 2
    }
4874
4875
    /**
4876
     * Converts a UTF-8 character to HTML Numbered Entity like "&#123;".
4877
     *
4878
     * @param string $char             <p>The Unicode character to be encoded as numbered entity.</p>
4879
     * @param bool   $keep_ascii_chars <p>Set to <strong>true</strong> to keep ASCII chars.</>
4880
     * @param string $encoding         [optional] <p>Set the charset for e.g. "mb_" function</p>
4881
     *
4882
     * @return string the HTML numbered entity
4883
     */
4884
    public static function single_chr_html_encode(
4885
        string $char,
4886
        bool $keep_ascii_chars = false,
4887
        string $encoding = 'UTF-8'
4888
    ): string {
4889 2
        if ($char === '') {
4890 2
            return '';
4891
        }
4892
4893
        if (
4894 2
            $keep_ascii_chars === true
4895
            &&
4896 2
            ASCII::is_ascii($char) === true
4897
        ) {
4898 2
            return $char;
4899
        }
4900
4901 2
        return '&#' . self::ord($char, $encoding) . ';';
4902
    }
4903
4904
    /**
4905
     * @param string $str
4906
     * @param int    $tab_length
4907
     *
4908
     * @return string
4909
     */
4910
    public static function spaces_to_tabs(string $str, int $tab_length = 4): string
4911
    {
4912 5
        if ($tab_length === 4) {
4913 3
            $tab = '    ';
4914 2
        } elseif ($tab_length === 2) {
4915 1
            $tab = '  ';
4916
        } else {
4917 1
            $tab = \str_repeat(' ', $tab_length);
4918
        }
4919
4920 5
        return \str_replace($tab, "\t", $str);
4921
    }
4922
4923
    /**
4924
     * alias for "UTF8::str_split()"
4925
     *
4926
     * @param string|string[] $str
4927
     * @param int             $length
4928
     * @param bool            $clean_utf8
4929
     *
4930
     * @return string[]
4931
     *
4932
     * @see UTF8::str_split()
4933
     * @deprecated <p>please use "UTF8::str_split()"</p>
4934
     */
4935
    public static function split(
4936
        $str,
4937
        int $length = 1,
4938
        bool $clean_utf8 = false
4939
    ): array {
4940 9
        return self::str_split($str, $length, $clean_utf8);
4941
    }
4942
4943
    /**
4944
     * alias for "UTF8::str_starts_with()"
4945
     *
4946
     * @param string $haystack
4947
     * @param string $needle
4948
     *
4949
     * @return bool
4950
     *
4951
     * @see UTF8::str_starts_with()
4952
     * @deprecated <p>please use "UTF8::str_starts_with()"</p>
4953
     */
4954
    public static function str_begins(string $haystack, string $needle): bool
4955
    {
4956
        return self::str_starts_with($haystack, $needle);
4957
    }
4958
4959
    /**
4960
     * Returns a camelCase version of the string. Trims surrounding spaces,
4961
     * capitalizes letters following digits, spaces, dashes and underscores,
4962
     * and removes spaces, dashes, as well as underscores.
4963
     *
4964
     * @param string      $str                           <p>The input string.</p>
4965
     * @param string      $encoding                      [optional] <p>Default: 'UTF-8'</p>
4966
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
4967
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
4968
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
4969
     *
4970
     * @return string
4971
     */
4972
    public static function str_camelize(
4973
        string $str,
4974
        string $encoding = 'UTF-8',
4975
        bool $clean_utf8 = false,
4976
        string $lang = null,
4977
        bool $try_to_keep_the_string_length = false
4978
    ): string {
4979 32
        if ($clean_utf8 === true) {
4980
            $str = self::clean($str);
4981
        }
4982
4983 32
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
4984 26
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
4985
        }
4986
4987 32
        $str = self::lcfirst(
4988 32
            \trim($str),
4989 32
            $encoding,
4990 32
            false,
4991 32
            $lang,
4992 32
            $try_to_keep_the_string_length
4993
        );
4994 32
        $str = (string) \preg_replace('/^[-_]+/', '', $str);
4995
4996 32
        $use_mb_functions = $lang === null && $try_to_keep_the_string_length === false;
4997
4998 32
        $str = (string) \preg_replace_callback(
4999 32
            '/[-_\\s]+(.)?/u',
5000
            /**
5001
             * @param array $match
5002
             *
5003
             * @return string
5004
             */
5005
            static function (array $match) use ($use_mb_functions, $encoding, $lang, $try_to_keep_the_string_length): string {
5006 27
                if (isset($match[1])) {
5007 27
                    if ($use_mb_functions === true) {
5008 27
                        if ($encoding === 'UTF-8') {
5009 27
                            return \mb_strtoupper($match[1]);
5010
                        }
5011
5012
                        return \mb_strtoupper($match[1], $encoding);
5013
                    }
5014
5015
                    return self::strtoupper($match[1], $encoding, false, $lang, $try_to_keep_the_string_length);
5016
                }
5017
5018 1
                return '';
5019 32
            },
5020 32
            $str
5021
        );
5022
5023 32
        return (string) \preg_replace_callback(
5024 32
            '/[\\p{N}]+(.)?/u',
5025
            /**
5026
             * @param array $match
5027
             *
5028
             * @return string
5029
             */
5030
            static function (array $match) use ($use_mb_functions, $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length): string {
5031 6
                if ($use_mb_functions === true) {
5032 6
                    if ($encoding === 'UTF-8') {
5033 6
                        return \mb_strtoupper($match[0]);
5034
                    }
5035
5036
                    return \mb_strtoupper($match[0], $encoding);
5037
                }
5038
5039
                return self::strtoupper($match[0], $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
5040 32
            },
5041 32
            $str
5042
        );
5043
    }
5044
5045
    /**
5046
     * Returns the string with the first letter of each word capitalized,
5047
     * except for when the word is a name which shouldn't be capitalized.
5048
     *
5049
     * @param string $str
5050
     *
5051
     * @return string string with $str capitalized
5052
     */
5053
    public static function str_capitalize_name(string $str): string
5054
    {
5055 1
        return self::str_capitalize_name_helper(
5056 1
            self::str_capitalize_name_helper(
5057 1
                self::collapse_whitespace($str),
5058 1
                ' '
5059
            ),
5060 1
            '-'
5061
        );
5062
    }
5063
5064
    /**
5065
     * Returns true if the string contains $needle, false otherwise. By default
5066
     * the comparison is case-sensitive, but can be made insensitive by setting
5067
     * $case_sensitive to false.
5068
     *
5069
     * @param string $haystack       <p>The input string.</p>
5070
     * @param string $needle         <p>Substring to look for.</p>
5071
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
5072
     *
5073
     * @return bool whether or not $haystack contains $needle
5074
     */
5075
    public static function str_contains(
5076
        string $haystack,
5077
        string $needle,
5078
        bool $case_sensitive = true
5079
    ): bool {
5080 21
        if ($case_sensitive) {
5081 11
            return \strpos($haystack, $needle) !== false;
5082
        }
5083
5084 10
        return \mb_stripos($haystack, $needle) !== false;
5085
    }
5086
5087
    /**
5088
     * Returns true if the string contains all $needles, false otherwise. By
5089
     * default the comparison is case-sensitive, but can be made insensitive by
5090
     * setting $case_sensitive to false.
5091
     *
5092
     * @param string $haystack       <p>The input string.</p>
5093
     * @param array  $needles        <p>SubStrings to look for.</p>
5094
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
5095
     *
5096
     * @return bool whether or not $haystack contains $needle
5097
     */
5098
    public static function str_contains_all(
5099
        string $haystack,
5100
        array $needles,
5101
        bool $case_sensitive = true
5102
    ): bool {
5103 44
        if ($haystack === '' || $needles === []) {
5104 1
            return false;
5105
        }
5106
5107
        /** @noinspection LoopWhichDoesNotLoopInspection */
5108 43
        foreach ($needles as &$needle) {
5109 43
            if (!$needle) {
5110 1
                return false;
5111
            }
5112
5113 42
            if ($case_sensitive) {
5114 22
                return \strpos($haystack, $needle) !== false;
5115
            }
5116
5117 20
            return \mb_stripos($haystack, $needle) !== false;
5118
        }
5119
5120
        return true;
5121
    }
5122
5123
    /**
5124
     * Returns true if the string contains any $needles, false otherwise. By
5125
     * default the comparison is case-sensitive, but can be made insensitive by
5126
     * setting $case_sensitive to false.
5127
     *
5128
     * @param string $haystack       <p>The input string.</p>
5129
     * @param array  $needles        <p>SubStrings to look for.</p>
5130
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
5131
     *
5132
     * @return bool
5133
     *              Whether or not $str contains $needle
5134
     */
5135
    public static function str_contains_any(
5136
        string $haystack,
5137
        array $needles,
5138
        bool $case_sensitive = true
5139
    ): bool {
5140 46
        if ($haystack === '' || $needles === []) {
5141 1
            return false;
5142
        }
5143
5144
        /** @noinspection LoopWhichDoesNotLoopInspection */
5145 45
        foreach ($needles as &$needle) {
5146 45
            if (!$needle) {
5147
                continue;
5148
            }
5149
5150 45
            if ($case_sensitive) {
5151 25
                if (\strpos($haystack, $needle) !== false) {
5152 14
                    return true;
5153
                }
5154
5155 13
                continue;
5156
            }
5157
5158 20
            if (\mb_stripos($haystack, $needle) !== false) {
5159 20
                return true;
5160
            }
5161
        }
5162
5163 19
        return false;
5164
    }
5165
5166
    /**
5167
     * Returns a lowercase and trimmed string separated by dashes. Dashes are
5168
     * inserted before uppercase characters (with the exception of the first
5169
     * character of the string), and in place of spaces as well as underscores.
5170
     *
5171
     * @param string $str      <p>The input string.</p>
5172
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
5173
     *
5174
     * @return string
5175
     */
5176
    public static function str_dasherize(string $str, string $encoding = 'UTF-8'): string
5177
    {
5178 19
        return self::str_delimit($str, '-', $encoding);
5179
    }
5180
5181
    /**
5182
     * Returns a lowercase and trimmed string separated by the given delimiter.
5183
     * Delimiters are inserted before uppercase characters (with the exception
5184
     * of the first character of the string), and in place of spaces, dashes,
5185
     * and underscores. Alpha delimiters are not converted to lowercase.
5186
     *
5187
     * @param string      $str                           <p>The input string.</p>
5188
     * @param string      $delimiter                     <p>Sequence used to separate parts of the string.</p>
5189
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
5190
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
5191
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt,
5192
     *                                                   tr</p>
5193
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ ->
5194
     *                                                   ß</p>
5195
     *
5196
     * @return string
5197
     */
5198
    public static function str_delimit(
5199
        string $str,
5200
        string $delimiter,
5201
        string $encoding = 'UTF-8',
5202
        bool $clean_utf8 = false,
5203
        string $lang = null,
5204
        bool $try_to_keep_the_string_length = false
5205
    ): string {
5206 49
        if (self::$SUPPORT['mbstring'] === true) {
5207
            /** @noinspection PhpComposerExtensionStubsInspection */
5208 49
            $str = (string) \mb_ereg_replace('\\B(\\p{Lu})', '-\1', \trim($str));
5209
5210 49
            $use_mb_functions = $lang === null && $try_to_keep_the_string_length === false;
5211 49
            if ($use_mb_functions === true && $encoding === 'UTF-8') {
5212 22
                $str = \mb_strtolower($str);
5213
            } else {
5214 27
                $str = self::strtolower($str, $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
5215
            }
5216
5217
            /** @noinspection PhpComposerExtensionStubsInspection */
5218 49
            return (string) \mb_ereg_replace('[\\-_\\s]+', $delimiter, $str);
5219
        }
5220
5221
        $str = (string) \preg_replace('/\\B(\\p{Lu})/u', '-\1', \trim($str));
5222
5223
        $use_mb_functions = $lang === null && $try_to_keep_the_string_length === false;
5224
        if ($use_mb_functions === true && $encoding === 'UTF-8') {
5225
            $str = \mb_strtolower($str);
5226
        } else {
5227
            $str = self::strtolower($str, $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
5228
        }
5229
5230
        return (string) \preg_replace('/[\\-_\\s]+/u', $delimiter, $str);
5231
    }
5232
5233
    /**
5234
     * Optimized "mb_detect_encoding()"-function -> with support for UTF-16 and UTF-32.
5235
     *
5236
     * @param string $str <p>The input string.</p>
5237
     *
5238
     * @return false|string
5239
     *                      The detected string-encoding e.g. UTF-8 or UTF-16BE,<br>
5240
     *                      otherwise it will return false e.g. for BINARY or not detected encoding.
5241
     */
5242
    public static function str_detect_encoding($str)
5243
    {
5244
        // init
5245 30
        $str = (string) $str;
5246
5247
        //
5248
        // 1.) check binary strings (010001001...) like UTF-16 / UTF-32 / PDF / Images / ...
5249
        //
5250
5251 30
        if (self::is_binary($str, true) === true) {
5252 11
            $is_utf32 = self::is_utf32($str, false);
5253 11
            if ($is_utf32 === 1) {
5254
                return 'UTF-32LE';
5255
            }
5256 11
            if ($is_utf32 === 2) {
5257 1
                return 'UTF-32BE';
5258
            }
5259
5260 11
            $is_utf16 = self::is_utf16($str, false);
5261 11
            if ($is_utf16 === 1) {
5262 3
                return 'UTF-16LE';
5263
            }
5264 11
            if ($is_utf16 === 2) {
5265 2
                return 'UTF-16BE';
5266
            }
5267
5268
            // is binary but not "UTF-16" or "UTF-32"
5269 9
            return false;
5270
        }
5271
5272
        //
5273
        // 2.) simple check for ASCII chars
5274
        //
5275
5276 26
        if (ASCII::is_ascii($str) === true) {
5277 10
            return 'ASCII';
5278
        }
5279
5280
        //
5281
        // 3.) simple check for UTF-8 chars
5282
        //
5283
5284 26
        if (self::is_utf8_string($str) === true) {
5285 19
            return 'UTF-8';
5286
        }
5287
5288
        //
5289
        // 4.) check via "mb_detect_encoding()"
5290
        //
5291
        // INFO: UTF-16, UTF-32, UCS2 and UCS4, encoding detection will fail always with "mb_detect_encoding()"
5292
5293
        $encoding_detecting_order = [
5294 15
            'ISO-8859-1',
5295
            'ISO-8859-2',
5296
            'ISO-8859-3',
5297
            'ISO-8859-4',
5298
            'ISO-8859-5',
5299
            'ISO-8859-6',
5300
            'ISO-8859-7',
5301
            'ISO-8859-8',
5302
            'ISO-8859-9',
5303
            'ISO-8859-10',
5304
            'ISO-8859-13',
5305
            'ISO-8859-14',
5306
            'ISO-8859-15',
5307
            'ISO-8859-16',
5308
            'WINDOWS-1251',
5309
            'WINDOWS-1252',
5310
            'WINDOWS-1254',
5311
            'CP932',
5312
            'CP936',
5313
            'CP950',
5314
            'CP866',
5315
            'CP850',
5316
            'CP51932',
5317
            'CP50220',
5318
            'CP50221',
5319
            'CP50222',
5320
            'ISO-2022-JP',
5321
            'ISO-2022-KR',
5322
            'JIS',
5323
            'JIS-ms',
5324
            'EUC-CN',
5325
            'EUC-JP',
5326
        ];
5327
5328 15
        if (self::$SUPPORT['mbstring'] === true) {
5329
            // info: do not use the symfony polyfill here
5330 15
            $encoding = \mb_detect_encoding($str, $encoding_detecting_order, true);
5331 15
            if ($encoding) {
5332 15
                return $encoding;
5333
            }
5334
        }
5335
5336
        //
5337
        // 5.) check via "iconv()"
5338
        //
5339
5340
        if (self::$ENCODINGS === null) {
5341
            self::$ENCODINGS = self::getData('encodings');
5342
        }
5343
5344
        foreach (self::$ENCODINGS as $encoding_tmp) {
5345
            // INFO: //IGNORE but still throw notice
5346
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
5347
            if ((string) @\iconv($encoding_tmp, $encoding_tmp . '//IGNORE', $str) === $str) {
5348
                return $encoding_tmp;
5349
            }
5350
        }
5351
5352
        return false;
5353
    }
5354
5355
    /**
5356
     * alias for "UTF8::str_ends_with()"
5357
     *
5358
     * @param string $haystack
5359
     * @param string $needle
5360
     *
5361
     * @return bool
5362
     *
5363
     * @see UTF8::str_ends_with()
5364
     * @deprecated <p>please use "UTF8::str_ends_with()"</p>
5365
     */
5366
    public static function str_ends(string $haystack, string $needle): bool
5367
    {
5368
        return self::str_ends_with($haystack, $needle);
5369
    }
5370
5371
    /**
5372
     * Check if the string ends with the given substring.
5373
     *
5374
     * @param string $haystack <p>The string to search in.</p>
5375
     * @param string $needle   <p>The substring to search for.</p>
5376
     *
5377
     * @return bool
5378
     */
5379
    public static function str_ends_with(string $haystack, string $needle): bool
5380
    {
5381 9
        if ($needle === '') {
5382 2
            return true;
5383
        }
5384
5385 9
        if ($haystack === '') {
5386
            return false;
5387
        }
5388
5389 9
        return \substr($haystack, -\strlen($needle)) === $needle;
5390
    }
5391
5392
    /**
5393
     * Returns true if the string ends with any of $substrings, false otherwise.
5394
     *
5395
     * - case-sensitive
5396
     *
5397
     * @param string   $str        <p>The input string.</p>
5398
     * @param string[] $substrings <p>Substrings to look for.</p>
5399
     *
5400
     * @return bool whether or not $str ends with $substring
5401
     */
5402
    public static function str_ends_with_any(string $str, array $substrings): bool
5403
    {
5404 7
        if ($substrings === []) {
5405
            return false;
5406
        }
5407
5408 7
        foreach ($substrings as &$substring) {
5409 7
            if (\substr($str, -\strlen($substring)) === $substring) {
5410 7
                return true;
5411
            }
5412
        }
5413
5414 6
        return false;
5415
    }
5416
5417
    /**
5418
     * Ensures that the string begins with $substring. If it doesn't, it's
5419
     * prepended.
5420
     *
5421
     * @param string $str       <p>The input string.</p>
5422
     * @param string $substring <p>The substring to add if not present.</p>
5423
     *
5424
     * @return string
5425
     */
5426
    public static function str_ensure_left(string $str, string $substring): string
5427
    {
5428
        if (
5429 10
            $substring !== ''
5430
            &&
5431 10
            \strpos($str, $substring) === 0
5432
        ) {
5433 6
            return $str;
5434
        }
5435
5436 4
        return $substring . $str;
5437
    }
5438
5439
    /**
5440
     * Ensures that the string ends with $substring. If it doesn't, it's appended.
5441
     *
5442
     * @param string $str       <p>The input string.</p>
5443
     * @param string $substring <p>The substring to add if not present.</p>
5444
     *
5445
     * @return string
5446
     */
5447
    public static function str_ensure_right(string $str, string $substring): string
5448
    {
5449
        if (
5450 10
            $str === ''
5451
            ||
5452 10
            $substring === ''
5453
            ||
5454 10
            \substr($str, -\strlen($substring)) !== $substring
5455
        ) {
5456 4
            $str .= $substring;
5457
        }
5458
5459 10
        return $str;
5460
    }
5461
5462
    /**
5463
     * Capitalizes the first word of the string, replaces underscores with
5464
     * spaces, and strips '_id'.
5465
     *
5466
     * @param string $str
5467
     *
5468
     * @return string
5469
     */
5470
    public static function str_humanize($str): string
5471
    {
5472 3
        $str = \str_replace(
5473
            [
5474 3
                '_id',
5475
                '_',
5476
            ],
5477
            [
5478 3
                '',
5479
                ' ',
5480
            ],
5481 3
            $str
5482
        );
5483
5484 3
        return self::ucfirst(\trim($str));
5485
    }
5486
5487
    /**
5488
     * alias for "UTF8::str_istarts_with()"
5489
     *
5490
     * @param string $haystack
5491
     * @param string $needle
5492
     *
5493
     * @return bool
5494
     *
5495
     * @see UTF8::str_istarts_with()
5496
     * @deprecated <p>please use "UTF8::str_istarts_with()"</p>
5497
     */
5498
    public static function str_ibegins(string $haystack, string $needle): bool
5499
    {
5500
        return self::str_istarts_with($haystack, $needle);
5501
    }
5502
5503
    /**
5504
     * alias for "UTF8::str_iends_with()"
5505
     *
5506
     * @param string $haystack
5507
     * @param string $needle
5508
     *
5509
     * @return bool
5510
     *
5511
     * @see UTF8::str_iends_with()
5512
     * @deprecated <p>please use "UTF8::str_iends_with()"</p>
5513
     */
5514
    public static function str_iends(string $haystack, string $needle): bool
5515
    {
5516
        return self::str_iends_with($haystack, $needle);
5517
    }
5518
5519
    /**
5520
     * Check if the string ends with the given substring, case-insensitive.
5521
     *
5522
     * @param string $haystack <p>The string to search in.</p>
5523
     * @param string $needle   <p>The substring to search for.</p>
5524
     *
5525
     * @return bool
5526
     */
5527
    public static function str_iends_with(string $haystack, string $needle): bool
5528
    {
5529 12
        if ($needle === '') {
5530 2
            return true;
5531
        }
5532
5533 12
        if ($haystack === '') {
5534
            return false;
5535
        }
5536
5537 12
        return self::strcasecmp(\substr($haystack, -\strlen($needle)), $needle) === 0;
5538
    }
5539
5540
    /**
5541
     * Returns true if the string ends with any of $substrings, false otherwise.
5542
     *
5543
     * - case-insensitive
5544
     *
5545
     * @param string   $str        <p>The input string.</p>
5546
     * @param string[] $substrings <p>Substrings to look for.</p>
5547
     *
5548
     * @return bool
5549
     *              <p>Whether or not $str ends with $substring.</p>
5550
     */
5551
    public static function str_iends_with_any(string $str, array $substrings): bool
5552
    {
5553 4
        if ($substrings === []) {
5554
            return false;
5555
        }
5556
5557 4
        foreach ($substrings as &$substring) {
5558 4
            if (self::str_iends_with($str, $substring)) {
5559 4
                return true;
5560
            }
5561
        }
5562
5563
        return false;
5564
    }
5565
5566
    /**
5567
     * Returns the index of the first occurrence of $needle in the string,
5568
     * and false if not found. Accepts an optional offset from which to begin
5569
     * the search.
5570
     *
5571
     * @param string $str      <p>The input string.</p>
5572
     * @param string $needle   <p>Substring to look for.</p>
5573
     * @param int    $offset   [optional] <p>Offset from which to search. Default: 0</p>
5574
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
5575
     *
5576
     * @return false|int
5577
     *                   <p>The occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
5578
     *
5579
     * @see UTF8::stripos()
5580
     * @deprecated <p>please use "UTF8::stripos()"</p>
5581
     */
5582
    public static function str_iindex_first(
5583
        string $str,
5584
        string $needle,
5585
        int $offset = 0,
5586
        string $encoding = 'UTF-8'
5587
    ) {
5588
        return self::stripos(
5589
            $str,
5590
            $needle,
5591
            $offset,
5592
            $encoding
5593
        );
5594
    }
5595
5596
    /**
5597
     * Returns the index of the last occurrence of $needle in the string,
5598
     * and false if not found. Accepts an optional offset from which to begin
5599
     * the search. Offsets may be negative to count from the last character
5600
     * in the string.
5601
     *
5602
     * @param string $str      <p>The input string.</p>
5603
     * @param string $needle   <p>Substring to look for.</p>
5604
     * @param int    $offset   [optional] <p>Offset from which to search. Default: 0</p>
5605
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
5606
     *
5607
     * @return false|int
5608
     *                   <p>The last occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
5609
     *
5610
     * @see UTF8::strripos()
5611
     * @deprecated <p>please use "UTF8::strripos()"</p>
5612
     */
5613
    public static function str_iindex_last(
5614
        string $str,
5615
        string $needle,
5616
        int $offset = 0,
5617
        string $encoding = 'UTF-8'
5618
    ) {
5619
        return self::strripos(
5620
            $str,
5621
            $needle,
5622
            $offset,
5623
            $encoding
5624
        );
5625
    }
5626
5627
    /**
5628
     * Returns the index of the first occurrence of $needle in the string,
5629
     * and false if not found. Accepts an optional offset from which to begin
5630
     * the search.
5631
     *
5632
     * @param string $str      <p>The input string.</p>
5633
     * @param string $needle   <p>Substring to look for.</p>
5634
     * @param int    $offset   [optional] <p>Offset from which to search. Default: 0</p>
5635
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
5636
     *
5637
     * @return false|int
5638
     *                   <p>The occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
5639
     *
5640
     * @see UTF8::strpos()
5641
     * @deprecated <p>please use "UTF8::strpos()"</p>
5642
     */
5643
    public static function str_index_first(
5644
        string $str,
5645
        string $needle,
5646
        int $offset = 0,
5647
        string $encoding = 'UTF-8'
5648
    ) {
5649 10
        return self::strpos(
5650 10
            $str,
5651 10
            $needle,
5652 10
            $offset,
5653 10
            $encoding
5654
        );
5655
    }
5656
5657
    /**
5658
     * Returns the index of the last occurrence of $needle in the string,
5659
     * and false if not found. Accepts an optional offset from which to begin
5660
     * the search. Offsets may be negative to count from the last character
5661
     * in the string.
5662
     *
5663
     * @param string $str      <p>The input string.</p>
5664
     * @param string $needle   <p>Substring to look for.</p>
5665
     * @param int    $offset   [optional] <p>Offset from which to search. Default: 0</p>
5666
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
5667
     *
5668
     * @return false|int
5669
     *                   <p>The last occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
5670
     *
5671
     * @see UTF8::strrpos()
5672
     * @deprecated <p>please use "UTF8::strrpos()"</p>
5673
     */
5674
    public static function str_index_last(
5675
        string $str,
5676
        string $needle,
5677
        int $offset = 0,
5678
        string $encoding = 'UTF-8'
5679
    ) {
5680 10
        return self::strrpos(
5681 10
            $str,
5682 10
            $needle,
5683 10
            $offset,
5684 10
            $encoding
5685
        );
5686
    }
5687
5688
    /**
5689
     * Inserts $substring into the string at the $index provided.
5690
     *
5691
     * @param string $str       <p>The input string.</p>
5692
     * @param string $substring <p>String to be inserted.</p>
5693
     * @param int    $index     <p>The index at which to insert the substring.</p>
5694
     * @param string $encoding  [optional] <p>Set the charset for e.g. "mb_" function</p>
5695
     *
5696
     * @return string
5697
     */
5698
    public static function str_insert(
5699
        string $str,
5700
        string $substring,
5701
        int $index,
5702
        string $encoding = 'UTF-8'
5703
    ): string {
5704 8
        if ($encoding === 'UTF-8') {
5705 4
            $len = (int) \mb_strlen($str);
5706 4
            if ($index > $len) {
5707
                return $str;
5708
            }
5709
5710
            /** @noinspection UnnecessaryCastingInspection */
5711 4
            return (string) \mb_substr($str, 0, $index) .
5712 4
                   $substring .
5713 4
                   (string) \mb_substr($str, $index, $len);
5714
        }
5715
5716 4
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
5717
5718 4
        $len = (int) self::strlen($str, $encoding);
5719 4
        if ($index > $len) {
5720 1
            return $str;
5721
        }
5722
5723 3
        return ((string) self::substr($str, 0, $index, $encoding)) .
5724 3
               $substring .
5725 3
               ((string) self::substr($str, $index, $len, $encoding));
5726
    }
5727
5728
    /**
5729
     * Case-insensitive and UTF-8 safe version of <function>str_replace</function>.
5730
     *
5731
     * @see http://php.net/manual/en/function.str-ireplace.php
5732
     *
5733
     * @param mixed $search  <p>
5734
     *                       Every replacement with search array is
5735
     *                       performed on the result of previous replacement.
5736
     *                       </p>
5737
     * @param mixed $replace <p>
5738
     *                       </p>
5739
     * @param mixed $subject <p>
5740
     *                       If subject is an array, then the search and
5741
     *                       replace is performed with every entry of
5742
     *                       subject, and the return value is an array as
5743
     *                       well.
5744
     *                       </p>
5745
     * @param int   $count   [optional] <p>
5746
     *                       The number of matched and replaced needles will
5747
     *                       be returned in count which is passed by
5748
     *                       reference.
5749
     *                       </p>
5750
     *
5751
     * @return mixed a string or an array of replacements
5752
     */
5753
    public static function str_ireplace($search, $replace, $subject, &$count = null)
5754
    {
5755 29
        $search = (array) $search;
5756
5757
        /** @noinspection AlterInForeachInspection */
5758 29
        foreach ($search as &$s) {
5759 29
            $s = (string) $s;
5760 29
            if ($s === '') {
5761 6
                $s = '/^(?<=.)$/';
5762
            } else {
5763 29
                $s = '/' . \preg_quote($s, '/') . '/ui';
5764
            }
5765
        }
5766
5767 29
        $subject = \preg_replace($search, $replace, $subject, -1, $replace);
5768 29
        $count = $replace; // used as reference parameter
5769
5770 29
        return $subject;
5771
    }
5772
5773
    /**
5774
     * Replaces $search from the beginning of string with $replacement.
5775
     *
5776
     * @param string $str         <p>The input string.</p>
5777
     * @param string $search      <p>The string to search for.</p>
5778
     * @param string $replacement <p>The replacement.</p>
5779
     *
5780
     * @return string string after the replacements
5781
     */
5782
    public static function str_ireplace_beginning(string $str, string $search, string $replacement): string
5783
    {
5784 17
        if ($str === '') {
5785 4
            if ($replacement === '') {
5786 2
                return '';
5787
            }
5788
5789 2
            if ($search === '') {
5790 2
                return $replacement;
5791
            }
5792
        }
5793
5794 13
        if ($search === '') {
5795 2
            return $str . $replacement;
5796
        }
5797
5798 11
        if (\stripos($str, $search) === 0) {
5799 10
            return $replacement . \substr($str, \strlen($search));
5800
        }
5801
5802 1
        return $str;
5803
    }
5804
5805
    /**
5806
     * Replaces $search from the ending of string with $replacement.
5807
     *
5808
     * @param string $str         <p>The input string.</p>
5809
     * @param string $search      <p>The string to search for.</p>
5810
     * @param string $replacement <p>The replacement.</p>
5811
     *
5812
     * @return string string after the replacements
5813
     */
5814
    public static function str_ireplace_ending(string $str, string $search, string $replacement): string
5815
    {
5816 17
        if ($str === '') {
5817 4
            if ($replacement === '') {
5818 2
                return '';
5819
            }
5820
5821 2
            if ($search === '') {
5822 2
                return $replacement;
5823
            }
5824
        }
5825
5826 13
        if ($search === '') {
5827 2
            return $str . $replacement;
5828
        }
5829
5830 11
        if (\stripos($str, $search, \strlen($str) - \strlen($search)) !== false) {
5831 9
            $str = \substr($str, 0, -\strlen($search)) . $replacement;
5832
        }
5833
5834 11
        return $str;
5835
    }
5836
5837
    /**
5838
     * Check if the string starts with the given substring, case-insensitive.
5839
     *
5840
     * @param string $haystack <p>The string to search in.</p>
5841
     * @param string $needle   <p>The substring to search for.</p>
5842
     *
5843
     * @return bool
5844
     */
5845
    public static function str_istarts_with(string $haystack, string $needle): bool
5846
    {
5847 12
        if ($needle === '') {
5848 2
            return true;
5849
        }
5850
5851 12
        if ($haystack === '') {
5852
            return false;
5853
        }
5854
5855 12
        return self::stripos($haystack, $needle) === 0;
5856
    }
5857
5858
    /**
5859
     * Returns true if the string begins with any of $substrings, false otherwise.
5860
     *
5861
     * - case-insensitive
5862
     *
5863
     * @param string $str        <p>The input string.</p>
5864
     * @param array  $substrings <p>Substrings to look for.</p>
5865
     *
5866
     * @return bool whether or not $str starts with $substring
5867
     */
5868
    public static function str_istarts_with_any(string $str, array $substrings): bool
5869
    {
5870 4
        if ($str === '') {
5871
            return false;
5872
        }
5873
5874 4
        if ($substrings === []) {
5875
            return false;
5876
        }
5877
5878 4
        foreach ($substrings as &$substring) {
5879 4
            if (self::str_istarts_with($str, $substring)) {
5880 4
                return true;
5881
            }
5882
        }
5883
5884
        return false;
5885
    }
5886
5887
    /**
5888
     * Gets the substring after the first occurrence of a separator.
5889
     *
5890
     * @param string $str       <p>The input string.</p>
5891
     * @param string $separator <p>The string separator.</p>
5892
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
5893
     *
5894
     * @return string
5895
     */
5896
    public static function str_isubstr_after_first_separator(
5897
        string $str,
5898
        string $separator,
5899
        string $encoding = 'UTF-8'
5900
    ): string {
5901 1
        if ($separator === '' || $str === '') {
5902 1
            return '';
5903
        }
5904
5905 1
        $offset = self::stripos($str, $separator);
5906 1
        if ($offset === false) {
5907 1
            return '';
5908
        }
5909
5910 1
        if ($encoding === 'UTF-8') {
5911 1
            return (string) \mb_substr(
5912 1
                $str,
5913 1
                $offset + (int) \mb_strlen($separator)
5914
            );
5915
        }
5916
5917
        return (string) self::substr(
5918
            $str,
5919
            $offset + (int) self::strlen($separator, $encoding),
5920
            null,
5921
            $encoding
5922
        );
5923
    }
5924
5925
    /**
5926
     * Gets the substring after the last occurrence of a separator.
5927
     *
5928
     * @param string $str       <p>The input string.</p>
5929
     * @param string $separator <p>The string separator.</p>
5930
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
5931
     *
5932
     * @return string
5933
     */
5934
    public static function str_isubstr_after_last_separator(
5935
        string $str,
5936
        string $separator,
5937
        string $encoding = 'UTF-8'
5938
    ): string {
5939 1
        if ($separator === '' || $str === '') {
5940 1
            return '';
5941
        }
5942
5943 1
        $offset = self::strripos($str, $separator);
5944 1
        if ($offset === false) {
5945 1
            return '';
5946
        }
5947
5948 1
        if ($encoding === 'UTF-8') {
5949 1
            return (string) \mb_substr(
5950 1
                $str,
5951 1
                $offset + (int) self::strlen($separator)
5952
            );
5953
        }
5954
5955
        return (string) self::substr(
5956
            $str,
5957
            $offset + (int) self::strlen($separator, $encoding),
5958
            null,
5959
            $encoding
5960
        );
5961
    }
5962
5963
    /**
5964
     * Gets the substring before the first occurrence of a separator.
5965
     *
5966
     * @param string $str       <p>The input string.</p>
5967
     * @param string $separator <p>The string separator.</p>
5968
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
5969
     *
5970
     * @return string
5971
     */
5972
    public static function str_isubstr_before_first_separator(
5973
        string $str,
5974
        string $separator,
5975
        string $encoding = 'UTF-8'
5976
    ): string {
5977 1
        if ($separator === '' || $str === '') {
5978 1
            return '';
5979
        }
5980
5981 1
        $offset = self::stripos($str, $separator);
5982 1
        if ($offset === false) {
5983 1
            return '';
5984
        }
5985
5986 1
        if ($encoding === 'UTF-8') {
5987 1
            return (string) \mb_substr($str, 0, $offset);
5988
        }
5989
5990
        return (string) self::substr($str, 0, $offset, $encoding);
5991
    }
5992
5993
    /**
5994
     * Gets the substring before the last occurrence of a separator.
5995
     *
5996
     * @param string $str       <p>The input string.</p>
5997
     * @param string $separator <p>The string separator.</p>
5998
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
5999
     *
6000
     * @return string
6001
     */
6002
    public static function str_isubstr_before_last_separator(
6003
        string $str,
6004
        string $separator,
6005
        string $encoding = 'UTF-8'
6006
    ): string {
6007 1
        if ($separator === '' || $str === '') {
6008 1
            return '';
6009
        }
6010
6011 1
        if ($encoding === 'UTF-8') {
6012 1
            $offset = \mb_strripos($str, $separator);
6013 1
            if ($offset === false) {
6014 1
                return '';
6015
            }
6016
6017 1
            return (string) \mb_substr($str, 0, $offset);
6018
        }
6019
6020
        $offset = self::strripos($str, $separator, 0, $encoding);
6021
        if ($offset === false) {
6022
            return '';
6023
        }
6024
6025
        return (string) self::substr($str, 0, $offset, $encoding);
6026
    }
6027
6028
    /**
6029
     * Gets the substring after (or before via "$before_needle") the first occurrence of the "$needle".
6030
     *
6031
     * @param string $str           <p>The input string.</p>
6032
     * @param string $needle        <p>The string to look for.</p>
6033
     * @param bool   $before_needle [optional] <p>Default: false</p>
6034
     * @param string $encoding      [optional] <p>Default: 'UTF-8'</p>
6035
     *
6036
     * @return string
6037
     */
6038
    public static function str_isubstr_first(
6039
        string $str,
6040
        string $needle,
6041
        bool $before_needle = false,
6042
        string $encoding = 'UTF-8'
6043
    ): string {
6044
        if (
6045 2
            $needle === ''
6046
            ||
6047 2
            $str === ''
6048
        ) {
6049 2
            return '';
6050
        }
6051
6052 2
        $part = self::stristr(
6053 2
            $str,
6054 2
            $needle,
6055 2
            $before_needle,
6056 2
            $encoding
6057
        );
6058 2
        if ($part === false) {
6059 2
            return '';
6060
        }
6061
6062 2
        return $part;
6063
    }
6064
6065
    /**
6066
     * Gets the substring after (or before via "$before_needle") the last occurrence of the "$needle".
6067
     *
6068
     * @param string $str           <p>The input string.</p>
6069
     * @param string $needle        <p>The string to look for.</p>
6070
     * @param bool   $before_needle [optional] <p>Default: false</p>
6071
     * @param string $encoding      [optional] <p>Default: 'UTF-8'</p>
6072
     *
6073
     * @return string
6074
     */
6075
    public static function str_isubstr_last(
6076
        string $str,
6077
        string $needle,
6078
        bool $before_needle = false,
6079
        string $encoding = 'UTF-8'
6080
    ): string {
6081
        if (
6082 1
            $needle === ''
6083
            ||
6084 1
            $str === ''
6085
        ) {
6086 1
            return '';
6087
        }
6088
6089 1
        $part = self::strrichr(
6090 1
            $str,
6091 1
            $needle,
6092 1
            $before_needle,
6093 1
            $encoding
6094
        );
6095 1
        if ($part === false) {
6096 1
            return '';
6097
        }
6098
6099 1
        return $part;
6100
    }
6101
6102
    /**
6103
     * Returns the last $n characters of the string.
6104
     *
6105
     * @param string $str      <p>The input string.</p>
6106
     * @param int    $n        <p>Number of characters to retrieve from the end.</p>
6107
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6108
     *
6109
     * @return string
6110
     */
6111
    public static function str_last_char(
6112
        string $str,
6113
        int $n = 1,
6114
        string $encoding = 'UTF-8'
6115
    ): string {
6116 12
        if ($str === '' || $n <= 0) {
6117 4
            return '';
6118
        }
6119
6120 8
        if ($encoding === 'UTF-8') {
6121 4
            return (string) \mb_substr($str, -$n);
6122
        }
6123
6124 4
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
6125
6126 4
        return (string) self::substr($str, -$n, null, $encoding);
6127
    }
6128
6129
    /**
6130
     * Limit the number of characters in a string.
6131
     *
6132
     * @param string $str        <p>The input string.</p>
6133
     * @param int    $length     [optional] <p>Default: 100</p>
6134
     * @param string $str_add_on [optional] <p>Default: …</p>
6135
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
6136
     *
6137
     * @return string
6138
     */
6139
    public static function str_limit(
6140
        string $str,
6141
        int $length = 100,
6142
        string $str_add_on = '…',
6143
        string $encoding = 'UTF-8'
6144
    ): string {
6145 2
        if ($str === '' || $length <= 0) {
6146 2
            return '';
6147
        }
6148
6149 2
        if ($encoding === 'UTF-8') {
6150 2
            if ((int) \mb_strlen($str) <= $length) {
6151 2
                return $str;
6152
            }
6153
6154
            /** @noinspection UnnecessaryCastingInspection */
6155 2
            return (string) \mb_substr($str, 0, $length - (int) self::strlen($str_add_on)) . $str_add_on;
6156
        }
6157
6158
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
6159
6160
        if ((int) self::strlen($str, $encoding) <= $length) {
6161
            return $str;
6162
        }
6163
6164
        return ((string) self::substr($str, 0, $length - (int) self::strlen($str_add_on), $encoding)) . $str_add_on;
6165
    }
6166
6167
    /**
6168
     * Limit the number of characters in a string, but also after the next word.
6169
     *
6170
     * @param string $str        <p>The input string.</p>
6171
     * @param int    $length     [optional] <p>Default: 100</p>
6172
     * @param string $str_add_on [optional] <p>Default: …</p>
6173
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
6174
     *
6175
     * @return string
6176
     */
6177
    public static function str_limit_after_word(
6178
        string $str,
6179
        int $length = 100,
6180
        string $str_add_on = '…',
6181
        string $encoding = 'UTF-8'
6182
    ): string {
6183 6
        if ($str === '' || $length <= 0) {
6184 2
            return '';
6185
        }
6186
6187 6
        if ($encoding === 'UTF-8') {
6188
            /** @noinspection UnnecessaryCastingInspection */
6189 2
            if ((int) \mb_strlen($str) <= $length) {
6190 2
                return $str;
6191
            }
6192
6193 2
            if (\mb_substr($str, $length - 1, 1) === ' ') {
6194 2
                return ((string) \mb_substr($str, 0, $length - 1)) . $str_add_on;
6195
            }
6196
6197 2
            $str = \mb_substr($str, 0, $length);
6198
6199 2
            $array = \explode(' ', $str);
6200 2
            \array_pop($array);
6201 2
            $new_str = \implode(' ', $array);
6202
6203 2
            if ($new_str === '') {
6204 2
                return ((string) \mb_substr($str, 0, $length - 1)) . $str_add_on;
6205
            }
6206
        } else {
6207 4
            if ((int) self::strlen($str, $encoding) <= $length) {
6208
                return $str;
6209
            }
6210
6211 4
            if (self::substr($str, $length - 1, 1, $encoding) === ' ') {
6212 3
                return ((string) self::substr($str, 0, $length - 1, $encoding)) . $str_add_on;
6213
            }
6214
6215
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
6216 1
            $str = self::substr($str, 0, $length, $encoding);
6217
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
6218 1
            if ($str === false) {
6219
                return '' . $str_add_on;
6220
            }
6221
6222 1
            $array = \explode(' ', $str);
6223 1
            \array_pop($array);
6224 1
            $new_str = \implode(' ', $array);
6225
6226 1
            if ($new_str === '') {
6227
                return ((string) self::substr($str, 0, $length - 1, $encoding)) . $str_add_on;
6228
            }
6229
        }
6230
6231 3
        return $new_str . $str_add_on;
6232
    }
6233
6234
    /**
6235
     * Returns the longest common prefix between the $str1 and $str2.
6236
     *
6237
     * @param string $str1     <p>The input sting.</p>
6238
     * @param string $str2     <p>Second string for comparison.</p>
6239
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6240
     *
6241
     * @return string
6242
     */
6243
    public static function str_longest_common_prefix(
6244
        string $str1,
6245
        string $str2,
6246
        string $encoding = 'UTF-8'
6247
    ): string {
6248
        // init
6249 10
        $longest_common_prefix = '';
6250
6251 10
        if ($encoding === 'UTF-8') {
6252 5
            $max_length = (int) \min(
6253 5
                \mb_strlen($str1),
6254 5
                \mb_strlen($str2)
6255
            );
6256
6257 5
            for ($i = 0; $i < $max_length; ++$i) {
6258 4
                $char = \mb_substr($str1, $i, 1);
6259
6260
                if (
6261 4
                    $char !== false
6262
                    &&
6263 4
                    $char === \mb_substr($str2, $i, 1)
6264
                ) {
6265 3
                    $longest_common_prefix .= $char;
6266
                } else {
6267 3
                    break;
6268
                }
6269
            }
6270
        } else {
6271 5
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
6272
6273 5
            $max_length = (int) \min(
6274 5
                self::strlen($str1, $encoding),
6275 5
                self::strlen($str2, $encoding)
6276
            );
6277
6278 5
            for ($i = 0; $i < $max_length; ++$i) {
6279 4
                $char = self::substr($str1, $i, 1, $encoding);
6280
6281
                if (
6282 4
                    $char !== false
6283
                    &&
6284 4
                    $char === self::substr($str2, $i, 1, $encoding)
6285
                ) {
6286 3
                    $longest_common_prefix .= $char;
6287
                } else {
6288 3
                    break;
6289
                }
6290
            }
6291
        }
6292
6293 10
        return $longest_common_prefix;
6294
    }
6295
6296
    /**
6297
     * Returns the longest common substring between the $str1 and $str2.
6298
     * In the case of ties, it returns that which occurs first.
6299
     *
6300
     * @param string $str1
6301
     * @param string $str2     <p>Second string for comparison.</p>
6302
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6303
     *
6304
     * @return string string with its $str being the longest common substring
6305
     */
6306
    public static function str_longest_common_substring(
6307
        string $str1,
6308
        string $str2,
6309
        string $encoding = 'UTF-8'
6310
    ): string {
6311 11
        if ($str1 === '' || $str2 === '') {
6312 2
            return '';
6313
        }
6314
6315
        // Uses dynamic programming to solve
6316
        // http://en.wikipedia.org/wiki/Longest_common_substring_problem
6317
6318 9
        if ($encoding === 'UTF-8') {
6319 4
            $str_length = (int) \mb_strlen($str1);
6320 4
            $other_length = (int) \mb_strlen($str2);
6321
        } else {
6322 5
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
6323
6324 5
            $str_length = (int) self::strlen($str1, $encoding);
6325 5
            $other_length = (int) self::strlen($str2, $encoding);
6326
        }
6327
6328
        // Return if either string is empty
6329 9
        if ($str_length === 0 || $other_length === 0) {
6330
            return '';
6331
        }
6332
6333 9
        $len = 0;
6334 9
        $end = 0;
6335 9
        $table = \array_fill(
6336 9
            0,
6337 9
            $str_length + 1,
6338 9
            \array_fill(0, $other_length + 1, 0)
6339
        );
6340
6341 9
        if ($encoding === 'UTF-8') {
6342 9
            for ($i = 1; $i <= $str_length; ++$i) {
6343 9
                for ($j = 1; $j <= $other_length; ++$j) {
6344 9
                    $str_char = \mb_substr($str1, $i - 1, 1);
6345 9
                    $other_char = \mb_substr($str2, $j - 1, 1);
6346
6347 9
                    if ($str_char === $other_char) {
6348 8
                        $table[$i][$j] = $table[$i - 1][$j - 1] + 1;
6349 8
                        if ($table[$i][$j] > $len) {
6350 8
                            $len = $table[$i][$j];
6351 8
                            $end = $i;
6352
                        }
6353
                    } else {
6354 9
                        $table[$i][$j] = 0;
6355
                    }
6356
                }
6357
            }
6358
        } else {
6359
            for ($i = 1; $i <= $str_length; ++$i) {
6360
                for ($j = 1; $j <= $other_length; ++$j) {
6361
                    $str_char = self::substr($str1, $i - 1, 1, $encoding);
6362
                    $other_char = self::substr($str2, $j - 1, 1, $encoding);
6363
6364
                    if ($str_char === $other_char) {
6365
                        $table[$i][$j] = $table[$i - 1][$j - 1] + 1;
6366
                        if ($table[$i][$j] > $len) {
6367
                            $len = $table[$i][$j];
6368
                            $end = $i;
6369
                        }
6370
                    } else {
6371
                        $table[$i][$j] = 0;
6372
                    }
6373
                }
6374
            }
6375
        }
6376
6377 9
        if ($encoding === 'UTF-8') {
6378 9
            return (string) \mb_substr($str1, $end - $len, $len);
6379
        }
6380
6381
        return (string) self::substr($str1, $end - $len, $len, $encoding);
6382
    }
6383
6384
    /**
6385
     * Returns the longest common suffix between the $str1 and $str2.
6386
     *
6387
     * @param string $str1
6388
     * @param string $str2     <p>Second string for comparison.</p>
6389
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6390
     *
6391
     * @return string
6392
     */
6393
    public static function str_longest_common_suffix(
6394
        string $str1,
6395
        string $str2,
6396
        string $encoding = 'UTF-8'
6397
    ): string {
6398 10
        if ($str1 === '' || $str2 === '') {
6399 2
            return '';
6400
        }
6401
6402 8
        if ($encoding === 'UTF-8') {
6403 4
            $max_length = (int) \min(
6404 4
                \mb_strlen($str1, $encoding),
6405 4
                \mb_strlen($str2, $encoding)
6406
            );
6407
6408 4
            $longest_common_suffix = '';
6409 4
            for ($i = 1; $i <= $max_length; ++$i) {
6410 4
                $char = \mb_substr($str1, -$i, 1);
6411
6412
                if (
6413 4
                    $char !== false
6414
                    &&
6415 4
                    $char === \mb_substr($str2, -$i, 1)
6416
                ) {
6417 3
                    $longest_common_suffix = $char . $longest_common_suffix;
6418
                } else {
6419 3
                    break;
6420
                }
6421
            }
6422
        } else {
6423 4
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
6424
6425 4
            $max_length = (int) \min(
6426 4
                self::strlen($str1, $encoding),
6427 4
                self::strlen($str2, $encoding)
6428
            );
6429
6430 4
            $longest_common_suffix = '';
6431 4
            for ($i = 1; $i <= $max_length; ++$i) {
6432 4
                $char = self::substr($str1, -$i, 1, $encoding);
6433
6434
                if (
6435 4
                    $char !== false
6436
                    &&
6437 4
                    $char === self::substr($str2, -$i, 1, $encoding)
6438
                ) {
6439 3
                    $longest_common_suffix = $char . $longest_common_suffix;
6440
                } else {
6441 3
                    break;
6442
                }
6443
            }
6444
        }
6445
6446 8
        return $longest_common_suffix;
6447
    }
6448
6449
    /**
6450
     * Returns true if $str matches the supplied pattern, false otherwise.
6451
     *
6452
     * @param string $str     <p>The input string.</p>
6453
     * @param string $pattern <p>Regex pattern to match against.</p>
6454
     *
6455
     * @return bool whether or not $str matches the pattern
6456
     */
6457
    public static function str_matches_pattern(string $str, string $pattern): bool
6458
    {
6459
        return (bool) \preg_match('/' . $pattern . '/u', $str);
6460
    }
6461
6462
    /**
6463
     * Returns whether or not a character exists at an index. Offsets may be
6464
     * negative to count from the last character in the string. Implements
6465
     * part of the ArrayAccess interface.
6466
     *
6467
     * @param string $str      <p>The input string.</p>
6468
     * @param int    $offset   <p>The index to check.</p>
6469
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6470
     *
6471
     * @return bool whether or not the index exists
6472
     */
6473
    public static function str_offset_exists(string $str, int $offset, string $encoding = 'UTF-8'): bool
6474
    {
6475
        // init
6476 6
        $length = (int) self::strlen($str, $encoding);
6477
6478 6
        if ($offset >= 0) {
6479 3
            return $length > $offset;
6480
        }
6481
6482 3
        return $length >= \abs($offset);
6483
    }
6484
6485
    /**
6486
     * Returns the character at the given index. Offsets may be negative to
6487
     * count from the last character in the string. Implements part of the
6488
     * ArrayAccess interface, and throws an OutOfBoundsException if the index
6489
     * does not exist.
6490
     *
6491
     * @param string $str      <p>The input string.</p>
6492
     * @param int    $index    <p>The <strong>index</strong> from which to retrieve the char.</p>
6493
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6494
     *
6495
     * @throws \OutOfBoundsException if the positive or negative offset does not exist
6496
     *
6497
     * @return string the character at the specified index
6498
     */
6499
    public static function str_offset_get(string $str, int $index, string $encoding = 'UTF-8'): string
6500
    {
6501
        // init
6502 2
        $length = (int) self::strlen($str);
6503
6504
        if (
6505 2
            ($index >= 0 && $length <= $index)
6506
            ||
6507 2
            $length < \abs($index)
6508
        ) {
6509 1
            throw new \OutOfBoundsException('No character exists at the index');
6510
        }
6511
6512 1
        return self::char_at($str, $index, $encoding);
6513
    }
6514
6515
    /**
6516
     * Pad a UTF-8 string to a given length with another string.
6517
     *
6518
     * @param string     $str        <p>The input string.</p>
6519
     * @param int        $pad_length <p>The length of return string.</p>
6520
     * @param string     $pad_string [optional] <p>String to use for padding the input string.</p>
6521
     * @param int|string $pad_type   [optional] <p>
6522
     *                               Can be <strong>STR_PAD_RIGHT</strong> (default), [or string "right"]<br>
6523
     *                               <strong>STR_PAD_LEFT</strong> [or string "left"] or<br>
6524
     *                               <strong>STR_PAD_BOTH</strong> [or string "both"]
6525
     *                               </p>
6526
     * @param string     $encoding   [optional] <p>Default: 'UTF-8'</p>
6527
     *
6528
     * @return string returns the padded string
6529
     */
6530
    public static function str_pad(
6531
        string $str,
6532
        int $pad_length,
6533
        string $pad_string = ' ',
6534
        $pad_type = \STR_PAD_RIGHT,
6535
        string $encoding = 'UTF-8'
6536
    ): string {
6537 41
        if ($pad_length === 0 || $pad_string === '') {
6538 1
            return $str;
6539
        }
6540
6541 41
        if ($pad_type !== (int) $pad_type) {
6542 13
            if ($pad_type === 'left') {
6543 3
                $pad_type = \STR_PAD_LEFT;
6544 10
            } elseif ($pad_type === 'right') {
6545 6
                $pad_type = \STR_PAD_RIGHT;
6546 4
            } elseif ($pad_type === 'both') {
6547 3
                $pad_type = \STR_PAD_BOTH;
6548
            } else {
6549 1
                throw new \InvalidArgumentException(
6550 1
                    'Pad expects $pad_type to be "STR_PAD_*" or ' . "to be one of 'left', 'right' or 'both'"
6551
                );
6552
            }
6553
        }
6554
6555 40
        if ($encoding === 'UTF-8') {
6556 25
            $str_length = (int) \mb_strlen($str);
6557
6558 25
            if ($pad_length >= $str_length) {
6559
                switch ($pad_type) {
6560 25
                    case \STR_PAD_LEFT:
6561 8
                        $ps_length = (int) \mb_strlen($pad_string);
6562
6563 8
                        $diff = ($pad_length - $str_length);
6564
6565 8
                        $pre = (string) \mb_substr(
6566 8
                            \str_repeat($pad_string, (int) \ceil($diff / $ps_length)),
6567 8
                            0,
6568 8
                            $diff
6569
                        );
6570 8
                        $post = '';
6571
6572 8
                        break;
6573
6574 20
                    case \STR_PAD_BOTH:
6575 14
                        $diff = ($pad_length - $str_length);
6576
6577 14
                        $ps_length_left = (int) \floor($diff / 2);
6578
6579 14
                        $ps_length_right = (int) \ceil($diff / 2);
6580
6581 14
                        $pre = (string) \mb_substr(
6582 14
                            \str_repeat($pad_string, $ps_length_left),
6583 14
                            0,
6584 14
                            $ps_length_left
6585
                        );
6586 14
                        $post = (string) \mb_substr(
6587 14
                            \str_repeat($pad_string, $ps_length_right),
6588 14
                            0,
6589 14
                            $ps_length_right
6590
                        );
6591
6592 14
                        break;
6593
6594 9
                    case \STR_PAD_RIGHT:
6595
                    default:
6596 9
                        $ps_length = (int) \mb_strlen($pad_string);
6597
6598 9
                        $diff = ($pad_length - $str_length);
6599
6600 9
                        $post = (string) \mb_substr(
6601 9
                            \str_repeat($pad_string, (int) \ceil($diff / $ps_length)),
6602 9
                            0,
6603 9
                            $diff
6604
                        );
6605 9
                        $pre = '';
6606
                }
6607
6608 25
                return $pre . $str . $post;
6609
            }
6610
6611 3
            return $str;
6612
        }
6613
6614 15
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
6615
6616 15
        $str_length = (int) self::strlen($str, $encoding);
6617
6618 15
        if ($pad_length >= $str_length) {
6619
            switch ($pad_type) {
6620 14
                case \STR_PAD_LEFT:
6621 5
                    $ps_length = (int) self::strlen($pad_string, $encoding);
6622
6623 5
                    $diff = ($pad_length - $str_length);
6624
6625 5
                    $pre = (string) self::substr(
6626 5
                        \str_repeat($pad_string, (int) \ceil($diff / $ps_length)),
6627 5
                        0,
6628 5
                        $diff,
6629 5
                        $encoding
6630
                    );
6631 5
                    $post = '';
6632
6633 5
                    break;
6634
6635 9
                case \STR_PAD_BOTH:
6636 3
                    $diff = ($pad_length - $str_length);
6637
6638 3
                    $ps_length_left = (int) \floor($diff / 2);
6639
6640 3
                    $ps_length_right = (int) \ceil($diff / 2);
6641
6642 3
                    $pre = (string) self::substr(
6643 3
                        \str_repeat($pad_string, $ps_length_left),
6644 3
                        0,
6645 3
                        $ps_length_left,
6646 3
                        $encoding
6647
                    );
6648 3
                    $post = (string) self::substr(
6649 3
                        \str_repeat($pad_string, $ps_length_right),
6650 3
                        0,
6651 3
                        $ps_length_right,
6652 3
                        $encoding
6653
                    );
6654
6655 3
                    break;
6656
6657 6
                case \STR_PAD_RIGHT:
6658
                default:
6659 6
                    $ps_length = (int) self::strlen($pad_string, $encoding);
6660
6661 6
                    $diff = ($pad_length - $str_length);
6662
6663 6
                    $post = (string) self::substr(
6664 6
                        \str_repeat($pad_string, (int) \ceil($diff / $ps_length)),
6665 6
                        0,
6666 6
                        $diff,
6667 6
                        $encoding
6668
                    );
6669 6
                    $pre = '';
6670
            }
6671
6672 14
            return $pre . $str . $post;
6673
        }
6674
6675 1
        return $str;
6676
    }
6677
6678
    /**
6679
     * Returns a new string of a given length such that both sides of the
6680
     * string are padded. Alias for "UTF8::str_pad()" with a $pad_type of 'both'.
6681
     *
6682
     * @param string $str
6683
     * @param int    $length   <p>Desired string length after padding.</p>
6684
     * @param string $pad_str  [optional] <p>String used to pad, defaults to space. Default: ' '</p>
6685
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6686
     *
6687
     * @return string
6688
     *                <p>The string with padding applied.</p>
6689
     */
6690
    public static function str_pad_both(
6691
        string $str,
6692
        int $length,
6693
        string $pad_str = ' ',
6694
        string $encoding = 'UTF-8'
6695
    ): string {
6696 11
        return self::str_pad(
6697 11
            $str,
6698 11
            $length,
6699 11
            $pad_str,
6700 11
            \STR_PAD_BOTH,
6701 11
            $encoding
6702
        );
6703
    }
6704
6705
    /**
6706
     * Returns a new string of a given length such that the beginning of the
6707
     * string is padded. Alias for "UTF8::str_pad()" with a $pad_type of 'left'.
6708
     *
6709
     * @param string $str
6710
     * @param int    $length   <p>Desired string length after padding.</p>
6711
     * @param string $pad_str  [optional] <p>String used to pad, defaults to space. Default: ' '</p>
6712
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6713
     *
6714
     * @return string
6715
     *                <p>The string with left padding.</p>
6716
     */
6717
    public static function str_pad_left(
6718
        string $str,
6719
        int $length,
6720
        string $pad_str = ' ',
6721
        string $encoding = 'UTF-8'
6722
    ): string {
6723 7
        return self::str_pad(
6724 7
            $str,
6725 7
            $length,
6726 7
            $pad_str,
6727 7
            \STR_PAD_LEFT,
6728 7
            $encoding
6729
        );
6730
    }
6731
6732
    /**
6733
     * Returns a new string of a given length such that the end of the string
6734
     * is padded. Alias for "UTF8::str_pad()" with a $pad_type of 'right'.
6735
     *
6736
     * @param string $str
6737
     * @param int    $length   <p>Desired string length after padding.</p>
6738
     * @param string $pad_str  [optional] <p>String used to pad, defaults to space. Default: ' '</p>
6739
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6740
     *
6741
     * @return string
6742
     *                <p>The string with right padding.</p>
6743
     */
6744
    public static function str_pad_right(
6745
        string $str,
6746
        int $length,
6747
        string $pad_str = ' ',
6748
        string $encoding = 'UTF-8'
6749
    ): string {
6750 7
        return self::str_pad(
6751 7
            $str,
6752 7
            $length,
6753 7
            $pad_str,
6754 7
            \STR_PAD_RIGHT,
6755 7
            $encoding
6756
        );
6757
    }
6758
6759
    /**
6760
     * Repeat a string.
6761
     *
6762
     * @param string $str        <p>
6763
     *                           The string to be repeated.
6764
     *                           </p>
6765
     * @param int    $multiplier <p>
6766
     *                           Number of time the input string should be
6767
     *                           repeated.
6768
     *                           </p>
6769
     *                           <p>
6770
     *                           multiplier has to be greater than or equal to 0.
6771
     *                           If the multiplier is set to 0, the function
6772
     *                           will return an empty string.
6773
     *                           </p>
6774
     *
6775
     * @return string
6776
     *                <p>The repeated string.</P>
6777
     */
6778
    public static function str_repeat(string $str, int $multiplier): string
6779
    {
6780 9
        $str = self::filter($str);
6781
6782 9
        return \str_repeat($str, $multiplier);
6783
    }
6784
6785
    /**
6786
     * INFO: This is only a wrapper for "str_replace()"  -> the original functions is already UTF-8 safe.
6787
     *
6788
     * Replace all occurrences of the search string with the replacement string
6789
     *
6790
     * @see http://php.net/manual/en/function.str-replace.php
6791
     *
6792
     * @param mixed $search  <p>
6793
     *                       The value being searched for, otherwise known as the needle.
6794
     *                       An array may be used to designate multiple needles.
6795
     *                       </p>
6796
     * @param mixed $replace <p>
6797
     *                       The replacement value that replaces found search
6798
     *                       values. An array may be used to designate multiple replacements.
6799
     *                       </p>
6800
     * @param mixed $subject <p>
6801
     *                       The string or array being searched and replaced on,
6802
     *                       otherwise known as the haystack.
6803
     *                       </p>
6804
     *                       <p>
6805
     *                       If subject is an array, then the search and
6806
     *                       replace is performed with every entry of
6807
     *                       subject, and the return value is an array as
6808
     *                       well.
6809
     *                       </p>
6810
     * @param int   $count   [optional] If passed, this will hold the number of matched and replaced needles
6811
     *
6812
     * @return mixed this function returns a string or an array with the replaced values
6813
     */
6814
    public static function str_replace(
6815
        $search,
6816
        $replace,
6817
        $subject,
6818
        int &$count = null
6819
    ) {
6820
        /**
6821
         * @psalm-suppress PossiblyNullArgument
6822
         */
6823 12
        return \str_replace(
6824 12
            $search,
6825 12
            $replace,
6826 12
            $subject,
6827 12
            $count
6828
        );
6829
    }
6830
6831
    /**
6832
     * Replaces $search from the beginning of string with $replacement.
6833
     *
6834
     * @param string $str         <p>The input string.</p>
6835
     * @param string $search      <p>The string to search for.</p>
6836
     * @param string $replacement <p>The replacement.</p>
6837
     *
6838
     * @return string string after the replacements
6839
     */
6840
    public static function str_replace_beginning(
6841
        string $str,
6842
        string $search,
6843
        string $replacement
6844
    ): string {
6845 17
        if ($str === '') {
6846 4
            if ($replacement === '') {
6847 2
                return '';
6848
            }
6849
6850 2
            if ($search === '') {
6851 2
                return $replacement;
6852
            }
6853
        }
6854
6855 13
        if ($search === '') {
6856 2
            return $str . $replacement;
6857
        }
6858
6859 11
        if (\strpos($str, $search) === 0) {
6860 9
            return $replacement . \substr($str, \strlen($search));
6861
        }
6862
6863 2
        return $str;
6864
    }
6865
6866
    /**
6867
     * Replaces $search from the ending of string with $replacement.
6868
     *
6869
     * @param string $str         <p>The input string.</p>
6870
     * @param string $search      <p>The string to search for.</p>
6871
     * @param string $replacement <p>The replacement.</p>
6872
     *
6873
     * @return string string after the replacements
6874
     */
6875
    public static function str_replace_ending(
6876
        string $str,
6877
        string $search,
6878
        string $replacement
6879
    ): string {
6880 17
        if ($str === '') {
6881 4
            if ($replacement === '') {
6882 2
                return '';
6883
            }
6884
6885 2
            if ($search === '') {
6886 2
                return $replacement;
6887
            }
6888
        }
6889
6890 13
        if ($search === '') {
6891 2
            return $str . $replacement;
6892
        }
6893
6894 11
        if (\strpos($str, $search, \strlen($str) - \strlen($search)) !== false) {
6895 8
            $str = \substr($str, 0, -\strlen($search)) . $replacement;
6896
        }
6897
6898 11
        return $str;
6899
    }
6900
6901
    /**
6902
     * Replace the first "$search"-term with the "$replace"-term.
6903
     *
6904
     * @param string $search
6905
     * @param string $replace
6906
     * @param string $subject
6907
     *
6908
     * @return string
6909
     *
6910
     * @psalm-suppress InvalidReturnType
6911
     */
6912
    public static function str_replace_first(
6913
        string $search,
6914
        string $replace,
6915
        string $subject
6916
    ): string {
6917 2
        $pos = self::strpos($subject, $search);
6918
6919 2
        if ($pos !== false) {
6920
            /**
6921
             * @psalm-suppress InvalidReturnStatement
6922
             */
6923 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...
6924 2
                $subject,
6925 2
                $replace,
6926 2
                $pos,
6927 2
                (int) self::strlen($search)
6928
            );
6929
        }
6930
6931 2
        return $subject;
6932
    }
6933
6934
    /**
6935
     * Replace the last "$search"-term with the "$replace"-term.
6936
     *
6937
     * @param string $search
6938
     * @param string $replace
6939
     * @param string $subject
6940
     *
6941
     * @return string
6942
     *
6943
     * @psalm-suppress InvalidReturnType
6944
     */
6945
    public static function str_replace_last(
6946
        string $search,
6947
        string $replace,
6948
        string $subject
6949
    ): string {
6950 2
        $pos = self::strrpos($subject, $search);
6951 2
        if ($pos !== false) {
6952
            /**
6953
             * @psalm-suppress InvalidReturnStatement
6954
             */
6955 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...
6956 2
                $subject,
6957 2
                $replace,
6958 2
                $pos,
6959 2
                (int) self::strlen($search)
6960
            );
6961
        }
6962
6963 2
        return $subject;
6964
    }
6965
6966
    /**
6967
     * Shuffles all the characters in the string.
6968
     *
6969
     * PS: uses random algorithm which is weak for cryptography purposes
6970
     *
6971
     * @param string $str      <p>The input string</p>
6972
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6973
     *
6974
     * @return string the shuffled string
6975
     */
6976
    public static function str_shuffle(string $str, string $encoding = 'UTF-8'): string
6977
    {
6978 5
        if ($encoding === 'UTF-8') {
6979 5
            $indexes = \range(0, (int) \mb_strlen($str) - 1);
6980
            /** @noinspection NonSecureShuffleUsageInspection */
6981 5
            \shuffle($indexes);
6982
6983
            // init
6984 5
            $shuffled_str = '';
6985
6986 5
            foreach ($indexes as &$i) {
6987 5
                $tmp_sub_str = \mb_substr($str, $i, 1);
6988 5
                if ($tmp_sub_str !== false) {
6989 5
                    $shuffled_str .= $tmp_sub_str;
6990
                }
6991
            }
6992
        } else {
6993
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
6994
6995
            $indexes = \range(0, (int) self::strlen($str, $encoding) - 1);
6996
            /** @noinspection NonSecureShuffleUsageInspection */
6997
            \shuffle($indexes);
6998
6999
            // init
7000
            $shuffled_str = '';
7001
7002
            foreach ($indexes as &$i) {
7003
                $tmp_sub_str = self::substr($str, $i, 1, $encoding);
7004
                if ($tmp_sub_str !== false) {
7005
                    $shuffled_str .= $tmp_sub_str;
7006
                }
7007
            }
7008
        }
7009
7010 5
        return $shuffled_str;
7011
    }
7012
7013
    /**
7014
     * Returns the substring beginning at $start, and up to, but not including
7015
     * the index specified by $end. If $end is omitted, the function extracts
7016
     * the remaining string. If $end is negative, it is computed from the end
7017
     * of the string.
7018
     *
7019
     * @param string $str
7020
     * @param int    $start    <p>Initial index from which to begin extraction.</p>
7021
     * @param int    $end      [optional] <p>Index at which to end extraction. Default: null</p>
7022
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7023
     *
7024
     * @return false|string
7025
     *                      <p>The extracted substring.</p><p>If <i>str</i> is shorter than <i>start</i>
7026
     *                      characters long, <b>FALSE</b> will be returned.
7027
     */
7028
    public static function str_slice(
7029
        string $str,
7030
        int $start,
7031
        int $end = null,
7032
        string $encoding = 'UTF-8'
7033
    ) {
7034 18
        if ($encoding === 'UTF-8') {
7035 7
            if ($end === null) {
7036 1
                $length = (int) \mb_strlen($str);
7037 6
            } elseif ($end >= 0 && $end <= $start) {
7038 2
                return '';
7039 4
            } elseif ($end < 0) {
7040 1
                $length = (int) \mb_strlen($str) + $end - $start;
7041
            } else {
7042 3
                $length = $end - $start;
7043
            }
7044
7045 5
            return \mb_substr($str, $start, $length);
7046
        }
7047
7048 11
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
7049
7050 11
        if ($end === null) {
7051 5
            $length = (int) self::strlen($str, $encoding);
7052 6
        } elseif ($end >= 0 && $end <= $start) {
7053 2
            return '';
7054 4
        } elseif ($end < 0) {
7055 1
            $length = (int) self::strlen($str, $encoding) + $end - $start;
7056
        } else {
7057 3
            $length = $end - $start;
7058
        }
7059
7060 9
        return self::substr($str, $start, $length, $encoding);
7061
    }
7062
7063
    /**
7064
     * Convert a string to e.g.: "snake_case"
7065
     *
7066
     * @param string $str
7067
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7068
     *
7069
     * @return string string in snake_case
7070
     */
7071
    public static function str_snakeize(string $str, string $encoding = 'UTF-8'): string
7072
    {
7073 22
        if ($str === '') {
7074
            return '';
7075
        }
7076
7077 22
        $str = \str_replace(
7078 22
            '-',
7079 22
            '_',
7080 22
            self::normalize_whitespace($str)
7081
        );
7082
7083 22
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
7084 19
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
7085
        }
7086
7087 22
        $str = (string) \preg_replace_callback(
7088 22
            '/([\\p{N}|\\p{Lu}])/u',
7089
            /**
7090
             * @param string[] $matches
7091
             *
7092
             * @return string
7093
             */
7094
            static function (array $matches) use ($encoding): string {
7095 9
                $match = $matches[1];
7096 9
                $match_int = (int) $match;
7097
7098 9
                if ((string) $match_int === $match) {
7099 4
                    return '_' . $match . '_';
7100
                }
7101
7102 5
                if ($encoding === 'UTF-8') {
7103 5
                    return '_' . \mb_strtolower($match);
7104
                }
7105
7106
                return '_' . self::strtolower($match, $encoding);
7107 22
            },
7108 22
            $str
7109
        );
7110
7111 22
        $str = (string) \preg_replace(
7112
            [
7113 22
                '/\\s+/u',           // convert spaces to "_"
7114
                '/^\\s+|\\s+$/u', // trim leading & trailing spaces
7115
                '/_+/',                 // remove double "_"
7116
            ],
7117
            [
7118 22
                '_',
7119
                '',
7120
                '_',
7121
            ],
7122 22
            $str
7123
        );
7124
7125 22
        return \trim(\trim($str, '_')); // trim leading & trailing "_" + whitespace
7126
    }
7127
7128
    /**
7129
     * Sort all characters according to code points.
7130
     *
7131
     * @param string $str    <p>A UTF-8 string.</p>
7132
     * @param bool   $unique <p>Sort unique. If <strong>true</strong>, repeated characters are ignored.</p>
7133
     * @param bool   $desc   <p>If <strong>true</strong>, will sort characters in reverse code point order.</p>
7134
     *
7135
     * @return string string of sorted characters
7136
     */
7137
    public static function str_sort(string $str, bool $unique = false, bool $desc = false): string
7138
    {
7139 2
        $array = self::codepoints($str);
7140
7141 2
        if ($unique) {
7142 2
            $array = \array_flip(\array_flip($array));
7143
        }
7144
7145 2
        if ($desc) {
7146 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

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

7148
            \asort(/** @scrutinizer ignore-type */ $array);
Loading history...
7149
        }
7150
7151 2
        return self::string($array);
7152
    }
7153
7154
    /**
7155
     * Convert a string to an array of Unicode characters.
7156
     *
7157
     * @param int|int[]|string|string[] $str                     <p>The string to split into array.</p>
7158
     * @param int                       $length                  [optional] <p>Max character length of each array
7159
     *                                                           element.</p>
7160
     * @param bool                      $clean_utf8              [optional] <p>Remove non UTF-8 chars from the string.</p>
7161
     * @param bool                      $try_to_use_mb_functions [optional] <p>Set to false, if you don't want to use
7162
     *                                                           "mb_substr"</p>
7163
     *
7164
     * @return array
7165
     *               <p>An array containing chunks of the input.</p>
7166
     */
7167
    public static function str_split(
7168
        $str,
7169
        int $length = 1,
7170
        bool $clean_utf8 = false,
7171
        bool $try_to_use_mb_functions = true
7172
    ): array {
7173 89
        if ($length <= 0) {
7174 3
            return [];
7175
        }
7176
7177 88
        if (\is_array($str) === true) {
7178 2
            foreach ($str as $k => &$v) {
7179 2
                $v = self::str_split(
7180 2
                    $v,
7181 2
                    $length,
7182 2
                    $clean_utf8,
7183 2
                    $try_to_use_mb_functions
7184
                );
7185
            }
7186
7187 2
            return $str;
7188
        }
7189
7190
        // init
7191 88
        $str = (string) $str;
7192
7193 88
        if ($str === '') {
7194 13
            return [];
7195
        }
7196
7197 85
        if ($clean_utf8 === true) {
7198 19
            $str = self::clean($str);
7199
        }
7200
7201
        if (
7202 85
            $try_to_use_mb_functions === true
7203
            &&
7204 85
            self::$SUPPORT['mbstring'] === true
7205
        ) {
7206 81
            $i_max = \mb_strlen($str);
7207 81
            if ($i_max <= 127) {
7208 75
                $ret = [];
7209 75
                for ($i = 0; $i < $i_max; ++$i) {
7210 75
                    $ret[] = \mb_substr($str, $i, 1);
7211
                }
7212
            } else {
7213 16
                $return_array = [];
7214 16
                \preg_match_all('/./us', $str, $return_array);
7215 81
                $ret = $return_array[0] ?? [];
7216
            }
7217 23
        } elseif (self::$SUPPORT['pcre_utf8'] === true) {
7218 17
            $return_array = [];
7219 17
            \preg_match_all('/./us', $str, $return_array);
7220 17
            $ret = $return_array[0] ?? [];
7221
        } else {
7222
7223
            // fallback
7224
7225 8
            $ret = [];
7226 8
            $len = \strlen($str);
7227
7228
            /** @noinspection ForeachInvariantsInspection */
7229 8
            for ($i = 0; $i < $len; ++$i) {
7230 8
                if (($str[$i] & "\x80") === "\x00") {
7231 8
                    $ret[] = $str[$i];
7232
                } elseif (
7233 8
                    isset($str[$i + 1])
7234
                    &&
7235 8
                    ($str[$i] & "\xE0") === "\xC0"
7236
                ) {
7237 4
                    if (($str[$i + 1] & "\xC0") === "\x80") {
7238 4
                        $ret[] = $str[$i] . $str[$i + 1];
7239
7240 4
                        ++$i;
7241
                    }
7242
                } elseif (
7243 6
                    isset($str[$i + 2])
7244
                    &&
7245 6
                    ($str[$i] & "\xF0") === "\xE0"
7246
                ) {
7247
                    if (
7248 6
                        ($str[$i + 1] & "\xC0") === "\x80"
7249
                        &&
7250 6
                        ($str[$i + 2] & "\xC0") === "\x80"
7251
                    ) {
7252 6
                        $ret[] = $str[$i] . $str[$i + 1] . $str[$i + 2];
7253
7254 6
                        $i += 2;
7255
                    }
7256
                } elseif (
7257
                    isset($str[$i + 3])
7258
                    &&
7259
                    ($str[$i] & "\xF8") === "\xF0"
7260
                ) {
7261
                    if (
7262
                        ($str[$i + 1] & "\xC0") === "\x80"
7263
                        &&
7264
                        ($str[$i + 2] & "\xC0") === "\x80"
7265
                        &&
7266
                        ($str[$i + 3] & "\xC0") === "\x80"
7267
                    ) {
7268
                        $ret[] = $str[$i] . $str[$i + 1] . $str[$i + 2] . $str[$i + 3];
7269
7270
                        $i += 3;
7271
                    }
7272
                }
7273
            }
7274
        }
7275
7276 85
        if ($length > 1) {
7277 11
            $ret = \array_chunk($ret, $length);
7278
7279 11
            return \array_map(
7280
                static function (array &$item): string {
7281 11
                    return \implode('', $item);
7282 11
                },
7283 11
                $ret
7284
            );
7285
        }
7286
7287 78
        if (isset($ret[0]) && $ret[0] === '') {
7288
            return [];
7289
        }
7290
7291 78
        return $ret;
7292
    }
7293
7294
    /**
7295
     * Splits the string with the provided regular expression, returning an
7296
     * array of strings. An optional integer $limit will truncate the
7297
     * results.
7298
     *
7299
     * @param string $str
7300
     * @param string $pattern <p>The regex with which to split the string.</p>
7301
     * @param int    $limit   [optional] <p>Maximum number of results to return. Default: -1 === no limit</p>
7302
     *
7303
     * @return string[] an array of strings
7304
     */
7305
    public static function str_split_pattern(string $str, string $pattern, int $limit = -1): array
7306
    {
7307 16
        if ($limit === 0) {
7308 2
            return [];
7309
        }
7310
7311 14
        if ($pattern === '') {
7312 1
            return [$str];
7313
        }
7314
7315 13
        if (self::$SUPPORT['mbstring'] === true) {
7316 13
            if ($limit >= 0) {
7317
                /** @noinspection PhpComposerExtensionStubsInspection */
7318 8
                $result_tmp = \mb_split($pattern, $str);
7319
7320 8
                $result = [];
7321 8
                foreach ($result_tmp as $item_tmp) {
7322 8
                    if ($limit === 0) {
7323 4
                        break;
7324
                    }
7325 8
                    --$limit;
7326
7327 8
                    $result[] = $item_tmp;
7328
                }
7329
7330 8
                return $result;
7331
            }
7332
7333
            /** @noinspection PhpComposerExtensionStubsInspection */
7334 5
            return \mb_split($pattern, $str);
7335
        }
7336
7337
        if ($limit > 0) {
7338
            ++$limit;
7339
        } else {
7340
            $limit = -1;
7341
        }
7342
7343
        $array = \preg_split('/' . \preg_quote($pattern, '/') . '/u', $str, $limit);
7344
7345
        if ($array === false) {
7346
            return [];
7347
        }
7348
7349
        if ($limit > 0 && \count($array) === $limit) {
7350
            \array_pop($array);
7351
        }
7352
7353
        return $array;
7354
    }
7355
7356
    /**
7357
     * Check if the string starts with the given substring.
7358
     *
7359
     * @param string $haystack <p>The string to search in.</p>
7360
     * @param string $needle   <p>The substring to search for.</p>
7361
     *
7362
     * @return bool
7363
     */
7364
    public static function str_starts_with(string $haystack, string $needle): bool
7365
    {
7366 19
        if ($needle === '') {
7367 2
            return true;
7368
        }
7369
7370 19
        if ($haystack === '') {
7371
            return false;
7372
        }
7373
7374 19
        return \strpos($haystack, $needle) === 0;
7375
    }
7376
7377
    /**
7378
     * Returns true if the string begins with any of $substrings, false otherwise.
7379
     *
7380
     * - case-sensitive
7381
     *
7382
     * @param string $str        <p>The input string.</p>
7383
     * @param array  $substrings <p>Substrings to look for.</p>
7384
     *
7385
     * @return bool whether or not $str starts with $substring
7386
     */
7387
    public static function str_starts_with_any(string $str, array $substrings): bool
7388
    {
7389 8
        if ($str === '') {
7390
            return false;
7391
        }
7392
7393 8
        if ($substrings === []) {
7394
            return false;
7395
        }
7396
7397 8
        foreach ($substrings as &$substring) {
7398 8
            if (self::str_starts_with($str, $substring)) {
7399 8
                return true;
7400
            }
7401
        }
7402
7403 6
        return false;
7404
    }
7405
7406
    /**
7407
     * Gets the substring after the first occurrence of a separator.
7408
     *
7409
     * @param string $str       <p>The input string.</p>
7410
     * @param string $separator <p>The string separator.</p>
7411
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
7412
     *
7413
     * @return string
7414
     */
7415
    public static function str_substr_after_first_separator(string $str, string $separator, string $encoding = 'UTF-8'): string
7416
    {
7417 1
        if ($separator === '' || $str === '') {
7418 1
            return '';
7419
        }
7420
7421 1
        if ($encoding === 'UTF-8') {
7422 1
            $offset = \mb_strpos($str, $separator);
7423 1
            if ($offset === false) {
7424 1
                return '';
7425
            }
7426
7427 1
            return (string) \mb_substr(
7428 1
                $str,
7429 1
                $offset + (int) \mb_strlen($separator)
7430
            );
7431
        }
7432
7433
        $offset = self::strpos($str, $separator, 0, $encoding);
7434
        if ($offset === false) {
7435
            return '';
7436
        }
7437
7438
        return (string) \mb_substr(
7439
            $str,
7440
            $offset + (int) self::strlen($separator, $encoding),
7441
            null,
7442
            $encoding
7443
        );
7444
    }
7445
7446
    /**
7447
     * Gets the substring after the last occurrence of a separator.
7448
     *
7449
     * @param string $str       <p>The input string.</p>
7450
     * @param string $separator <p>The string separator.</p>
7451
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
7452
     *
7453
     * @return string
7454
     */
7455
    public static function str_substr_after_last_separator(string $str, string $separator, string $encoding = 'UTF-8'): string
7456
    {
7457 1
        if ($separator === '' || $str === '') {
7458 1
            return '';
7459
        }
7460
7461 1
        if ($encoding === 'UTF-8') {
7462 1
            $offset = \mb_strrpos($str, $separator);
7463 1
            if ($offset === false) {
7464 1
                return '';
7465
            }
7466
7467 1
            return (string) \mb_substr(
7468 1
                $str,
7469 1
                $offset + (int) \mb_strlen($separator)
7470
            );
7471
        }
7472
7473
        $offset = self::strrpos($str, $separator, 0, $encoding);
7474
        if ($offset === false) {
7475
            return '';
7476
        }
7477
7478
        return (string) self::substr(
7479
            $str,
7480
            $offset + (int) self::strlen($separator, $encoding),
7481
            null,
7482
            $encoding
7483
        );
7484
    }
7485
7486
    /**
7487
     * Gets the substring before the first occurrence of a separator.
7488
     *
7489
     * @param string $str       <p>The input string.</p>
7490
     * @param string $separator <p>The string separator.</p>
7491
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
7492
     *
7493
     * @return string
7494
     */
7495
    public static function str_substr_before_first_separator(
7496
        string $str,
7497
        string $separator,
7498
        string $encoding = 'UTF-8'
7499
    ): string {
7500 1
        if ($separator === '' || $str === '') {
7501 1
            return '';
7502
        }
7503
7504 1
        if ($encoding === 'UTF-8') {
7505 1
            $offset = \mb_strpos($str, $separator);
7506 1
            if ($offset === false) {
7507 1
                return '';
7508
            }
7509
7510 1
            return (string) \mb_substr(
7511 1
                $str,
7512 1
                0,
7513 1
                $offset
7514
            );
7515
        }
7516
7517
        $offset = self::strpos($str, $separator, 0, $encoding);
7518
        if ($offset === false) {
7519
            return '';
7520
        }
7521
7522
        return (string) self::substr(
7523
            $str,
7524
            0,
7525
            $offset,
7526
            $encoding
7527
        );
7528
    }
7529
7530
    /**
7531
     * Gets the substring before the last occurrence of a separator.
7532
     *
7533
     * @param string $str       <p>The input string.</p>
7534
     * @param string $separator <p>The string separator.</p>
7535
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
7536
     *
7537
     * @return string
7538
     */
7539
    public static function str_substr_before_last_separator(string $str, string $separator, string $encoding = 'UTF-8'): string
7540
    {
7541 1
        if ($separator === '' || $str === '') {
7542 1
            return '';
7543
        }
7544
7545 1
        if ($encoding === 'UTF-8') {
7546 1
            $offset = \mb_strrpos($str, $separator);
7547 1
            if ($offset === false) {
7548 1
                return '';
7549
            }
7550
7551 1
            return (string) \mb_substr(
7552 1
                $str,
7553 1
                0,
7554 1
                $offset
7555
            );
7556
        }
7557
7558
        $offset = self::strrpos($str, $separator, 0, $encoding);
7559
        if ($offset === false) {
7560
            return '';
7561
        }
7562
7563
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
7564
7565
        return (string) self::substr(
7566
            $str,
7567
            0,
7568
            $offset,
7569
            $encoding
7570
        );
7571
    }
7572
7573
    /**
7574
     * Gets the substring after (or before via "$before_needle") the first occurrence of the "$needle".
7575
     *
7576
     * @param string $str           <p>The input string.</p>
7577
     * @param string $needle        <p>The string to look for.</p>
7578
     * @param bool   $before_needle [optional] <p>Default: false</p>
7579
     * @param string $encoding      [optional] <p>Default: 'UTF-8'</p>
7580
     *
7581
     * @return string
7582
     */
7583
    public static function str_substr_first(
7584
        string $str,
7585
        string $needle,
7586
        bool $before_needle = false,
7587
        string $encoding = 'UTF-8'
7588
    ): string {
7589 2
        if ($str === '' || $needle === '') {
7590 2
            return '';
7591
        }
7592
7593 2
        if ($encoding === 'UTF-8') {
7594 2
            if ($before_needle === true) {
7595 1
                $part = \mb_strstr(
7596 1
                    $str,
7597 1
                    $needle,
7598 1
                    $before_needle
7599
                );
7600
            } else {
7601 1
                $part = \mb_strstr(
7602 1
                    $str,
7603 2
                    $needle
7604
                );
7605
            }
7606
        } else {
7607
            $part = self::strstr(
7608
                $str,
7609
                $needle,
7610
                $before_needle,
7611
                $encoding
7612
            );
7613
        }
7614
7615 2
        return $part === false ? '' : $part;
7616
    }
7617
7618
    /**
7619
     * Gets the substring after (or before via "$before_needle") the last occurrence of the "$needle".
7620
     *
7621
     * @param string $str           <p>The input string.</p>
7622
     * @param string $needle        <p>The string to look for.</p>
7623
     * @param bool   $before_needle [optional] <p>Default: false</p>
7624
     * @param string $encoding      [optional] <p>Default: 'UTF-8'</p>
7625
     *
7626
     * @return string
7627
     */
7628
    public static function str_substr_last(
7629
        string $str,
7630
        string $needle,
7631
        bool $before_needle = false,
7632
        string $encoding = 'UTF-8'
7633
    ): string {
7634 2
        if ($str === '' || $needle === '') {
7635 2
            return '';
7636
        }
7637
7638 2
        if ($encoding === 'UTF-8') {
7639 2
            if ($before_needle === true) {
7640 1
                $part = \mb_strrchr(
7641 1
                    $str,
7642 1
                    $needle,
7643 1
                    $before_needle
7644
                );
7645
            } else {
7646 1
                $part = \mb_strrchr(
7647 1
                    $str,
7648 2
                    $needle
7649
                );
7650
            }
7651
        } else {
7652
            $part = self::strrchr(
7653
                $str,
7654
                $needle,
7655
                $before_needle,
7656
                $encoding
7657
            );
7658
        }
7659
7660 2
        return $part === false ? '' : $part;
7661
    }
7662
7663
    /**
7664
     * Surrounds $str with the given substring.
7665
     *
7666
     * @param string $str
7667
     * @param string $substring <p>The substring to add to both sides.</P>
7668
     *
7669
     * @return string string with the substring both prepended and appended
7670
     */
7671
    public static function str_surround(string $str, string $substring): string
7672
    {
7673 5
        return $substring . $str . $substring;
7674
    }
7675
7676
    /**
7677
     * Returns a trimmed string with the first letter of each word capitalized.
7678
     * Also accepts an array, $ignore, allowing you to list words not to be
7679
     * capitalized.
7680
     *
7681
     * @param string              $str
7682
     * @param array|string[]|null $ignore                        [optional] <p>An array of words not to capitalize or null.
7683
     *                                                           Default: null</p>
7684
     * @param string              $encoding                      [optional] <p>Default: 'UTF-8'</p>
7685
     * @param bool                $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
7686
     * @param string|null         $lang                          [optional] <p>Set the language for special cases: az, el, lt,
7687
     *                                                           tr</p>
7688
     * @param bool                $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ ->
7689
     *                                                           ß</p>
7690
     * @param bool                $use_trim_first                [optional] <p>true === trim the input string, first</p>
7691
     * @param string|null         $word_define_chars             [optional] <p>An string of chars that will be used as whitespace separator === words.</p>
7692
     *
7693
     * @return string
7694
     *                <p>The titleized string.</p>
7695
     */
7696
    public static function str_titleize(
7697
        string $str,
7698
        array $ignore = null,
7699
        string $encoding = 'UTF-8',
7700
        bool $clean_utf8 = false,
7701
        string $lang = null,
7702
        bool $try_to_keep_the_string_length = false,
7703
        bool $use_trim_first = true,
7704
        string $word_define_chars = null
7705
    ): string {
7706 10
        if ($str === '') {
7707
            return '';
7708
        }
7709
7710 10
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
7711 9
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
7712
        }
7713
7714 10
        if ($use_trim_first === true) {
7715 10
            $str = \trim($str);
7716
        }
7717
7718 10
        if ($clean_utf8 === true) {
7719
            $str = self::clean($str);
7720
        }
7721
7722 10
        $use_mb_functions = $lang === null && $try_to_keep_the_string_length === false;
7723
7724 10
        if ($word_define_chars) {
7725 4
            $word_define_chars = \preg_quote($word_define_chars, '/');
7726
        } else {
7727 6
            $word_define_chars = '';
7728
        }
7729
7730 10
        $str = (string) \preg_replace_callback(
7731 10
            '/([^\\s' . $word_define_chars . ']+)/u',
7732
            static function (array $match) use ($try_to_keep_the_string_length, $lang, $ignore, $use_mb_functions, $encoding): string {
7733 10
                if ($ignore !== null && \in_array($match[0], $ignore, true)) {
7734 4
                    return $match[0];
7735
                }
7736
7737 10
                if ($use_mb_functions === true) {
7738 10
                    if ($encoding === 'UTF-8') {
7739 10
                        return \mb_strtoupper(\mb_substr($match[0], 0, 1))
7740 10
                               . \mb_strtolower(\mb_substr($match[0], 1));
7741
                    }
7742
7743
                    return \mb_strtoupper(\mb_substr($match[0], 0, 1, $encoding), $encoding)
7744
                           . \mb_strtolower(\mb_substr($match[0], 1, null, $encoding), $encoding);
7745
                }
7746
7747
                return self::ucfirst(
7748
                    self::strtolower(
7749
                        $match[0],
7750
                        $encoding,
7751
                        false,
7752
                        $lang,
7753
                        $try_to_keep_the_string_length
7754
                    ),
7755
                    $encoding,
7756
                    false,
7757
                    $lang,
7758
                    $try_to_keep_the_string_length
7759
                );
7760 10
            },
7761 10
            $str
7762
        );
7763
7764 10
        return $str;
7765
    }
7766
7767
    /**
7768
     * Returns a trimmed string in proper title case.
7769
     *
7770
     * Also accepts an array, $ignore, allowing you to list words not to be
7771
     * capitalized.
7772
     *
7773
     * Adapted from John Gruber's script.
7774
     *
7775
     * @see https://gist.github.com/gruber/9f9e8650d68b13ce4d78
7776
     *
7777
     * @param string $str
7778
     * @param array  $ignore   <p>An array of words not to capitalize.</p>
7779
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7780
     *
7781
     * @return string the titleized string
7782
     */
7783
    public static function str_titleize_for_humans(
7784
        string $str,
7785
        array $ignore = [],
7786
        string $encoding = 'UTF-8'
7787
    ): string {
7788 35
        if ($str === '') {
7789
            return '';
7790
        }
7791
7792
        $small_words = [
7793 35
            '(?<!q&)a',
7794
            'an',
7795
            'and',
7796
            'as',
7797
            'at(?!&t)',
7798
            'but',
7799
            'by',
7800
            'en',
7801
            'for',
7802
            'if',
7803
            'in',
7804
            'of',
7805
            'on',
7806
            'or',
7807
            'the',
7808
            'to',
7809
            'v[.]?',
7810
            'via',
7811
            'vs[.]?',
7812
        ];
7813
7814 35
        if ($ignore !== []) {
7815 1
            $small_words = \array_merge($small_words, $ignore);
7816
        }
7817
7818 35
        $small_words_rx = \implode('|', $small_words);
7819 35
        $apostrophe_rx = '(?x: [\'’] [[:lower:]]* )?';
7820
7821 35
        $str = \trim($str);
7822
7823 35
        if (self::has_lowercase($str) === false) {
7824 2
            $str = self::strtolower($str, $encoding);
7825
        }
7826
7827
        // the main substitutions
7828 35
        $str = (string) \preg_replace_callback(
7829
            '~\\b (_*) (?:                                                         # 1. Leading underscore and
7830
                        ( (?<=[ ][/\\\\]) [[:alpha:]]+ [-_[:alpha:]/\\\\]+ |              # 2. file path or 
7831 35
                          [-_[:alpha:]]+ [@.:] [-_[:alpha:]@.:/]+ ' . $apostrophe_rx . ' ) #    URL, domain, or email
7832
                        |
7833 35
                        ( (?i: ' . $small_words_rx . ' ) ' . $apostrophe_rx . ' )            # 3. or small word (case-insensitive)
7834
                        |
7835 35
                        ( [[:alpha:]] [[:lower:]\'’()\[\]{}]* ' . $apostrophe_rx . ' )     # 4. or word w/o internal caps
7836
                        |
7837 35
                        ( [[:alpha:]] [[:alpha:]\'’()\[\]{}]* ' . $apostrophe_rx . ' )     # 5. or some other word
7838
                      ) (_*) \\b                                                          # 6. With trailing underscore
7839
                    ~ux',
7840
            /**
7841
             * @param string[] $matches
7842
             *
7843
             * @return string
7844
             */
7845
            static function (array $matches) use ($encoding): string {
7846
                // preserve leading underscore
7847 35
                $str = $matches[1];
7848 35
                if ($matches[2]) {
7849
                    // preserve URLs, domains, emails and file paths
7850 5
                    $str .= $matches[2];
7851 35
                } elseif ($matches[3]) {
7852
                    // lower-case small words
7853 25
                    $str .= self::strtolower($matches[3], $encoding);
7854 35
                } elseif ($matches[4]) {
7855
                    // capitalize word w/o internal caps
7856 34
                    $str .= static::ucfirst($matches[4], $encoding);
7857
                } else {
7858
                    // preserve other kinds of word (iPhone)
7859 7
                    $str .= $matches[5];
7860
                }
7861
                // preserve trailing underscore
7862 35
                $str .= $matches[6];
7863
7864 35
                return $str;
7865 35
            },
7866 35
            $str
7867
        );
7868
7869
        // Exceptions for small words: capitalize at start of title...
7870 35
        $str = (string) \preg_replace_callback(
7871
            '~(  \\A [[:punct:]]*            # start of title...
7872
                      |  [:.;?!][ ]+                # or of subsentence...
7873
                      |  [ ][\'"“‘(\[][ ]* )        # or of inserted subphrase...
7874 35
                      ( ' . $small_words_rx . ' ) \\b # ...followed by small word
7875
                     ~uxi',
7876
            /**
7877
             * @param string[] $matches
7878
             *
7879
             * @return string
7880
             */
7881
            static function (array $matches) use ($encoding): string {
7882 11
                return $matches[1] . static::ucfirst($matches[2], $encoding);
7883 35
            },
7884 35
            $str
7885
        );
7886
7887
        // ...and end of title
7888 35
        $str = (string) \preg_replace_callback(
7889 35
            '~\\b ( ' . $small_words_rx . ' ) # small word...
7890
                      (?= [[:punct:]]* \Z          # ...at the end of the title...
7891
                      |   [\'"’”)\]] [ ] )         # ...or of an inserted subphrase?
7892
                     ~uxi',
7893
            /**
7894
             * @param string[] $matches
7895
             *
7896
             * @return string
7897
             */
7898
            static function (array $matches) use ($encoding): string {
7899 3
                return static::ucfirst($matches[1], $encoding);
7900 35
            },
7901 35
            $str
7902
        );
7903
7904
        // Exceptions for small words in hyphenated compound words.
7905
        // e.g. "in-flight" -> In-Flight
7906 35
        $str = (string) \preg_replace_callback(
7907
            '~\\b
7908
                        (?<! -)                   # Negative lookbehind for a hyphen; we do not want to match man-in-the-middle but do want (in-flight)
7909 35
                        ( ' . $small_words_rx . ' )
7910
                        (?= -[[:alpha:]]+)        # lookahead for "-someword"
7911
                       ~uxi',
7912
            /**
7913
             * @param string[] $matches
7914
             *
7915
             * @return string
7916
             */
7917
            static function (array $matches) use ($encoding): string {
7918
                return static::ucfirst($matches[1], $encoding);
7919 35
            },
7920 35
            $str
7921
        );
7922
7923
        // e.g. "Stand-in" -> "Stand-In" (Stand is already capped at this point)
7924 35
        $str = (string) \preg_replace_callback(
7925
            '~\\b
7926
                      (?<!…)                    # Negative lookbehind for a hyphen; we do not want to match man-in-the-middle but do want (stand-in)
7927
                      ( [[:alpha:]]+- )         # $1 = first word and hyphen, should already be properly capped
7928 35
                      ( ' . $small_words_rx . ' ) # ...followed by small word
7929
                      (?!	- )                 # Negative lookahead for another -
7930
                     ~uxi',
7931
            /**
7932
             * @param string[] $matches
7933
             *
7934
             * @return string
7935
             */
7936
            static function (array $matches) use ($encoding): string {
7937
                return $matches[1] . static::ucfirst($matches[2], $encoding);
7938 35
            },
7939 35
            $str
7940
        );
7941
7942 35
        return $str;
7943
    }
7944
7945
    /**
7946
     * Get a binary representation of a specific string.
7947
     *
7948
     * @param string $str <p>The input string.</p>
7949
     *
7950
     * @return false|string
7951
     *                      <p>false on error</p>
7952
     */
7953
    public static function str_to_binary(string $str)
7954
    {
7955
        /** @var array|false $value - needed for PhpStan (stubs error) */
7956 2
        $value = \unpack('H*', $str);
7957 2
        if ($value === false) {
7958
            return false;
7959
        }
7960
7961
        /** @noinspection OffsetOperationsInspection */
7962 2
        return \base_convert($value[1], 16, 2);
7963
    }
7964
7965
    /**
7966
     * @param string   $str
7967
     * @param bool     $remove_empty_values <p>Remove empty values.</p>
7968
     * @param int|null $remove_short_values <p>The min. string length or null to disable</p>
7969
     *
7970
     * @return string[]
7971
     */
7972
    public static function str_to_lines(string $str, bool $remove_empty_values = false, int $remove_short_values = null): array
7973
    {
7974 17
        if ($str === '') {
7975 1
            return $remove_empty_values === true ? [] : [''];
7976
        }
7977
7978 16
        if (self::$SUPPORT['mbstring'] === true) {
7979
            /** @noinspection PhpComposerExtensionStubsInspection */
7980 16
            $return = \mb_split("[\r\n]{1,2}", $str);
7981
        } else {
7982
            $return = \preg_split("/[\r\n]{1,2}/u", $str);
7983
        }
7984
7985 16
        if ($return === false) {
7986
            return $remove_empty_values === true ? [] : [''];
7987
        }
7988
7989
        if (
7990 16
            $remove_short_values === null
7991
            &&
7992 16
            $remove_empty_values === false
7993
        ) {
7994 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...
7995
        }
7996
7997
        return self::reduce_string_array(
7998
            $return,
7999
            $remove_empty_values,
8000
            $remove_short_values
8001
        );
8002
    }
8003
8004
    /**
8005
     * Convert a string into an array of words.
8006
     *
8007
     * @param string   $str
8008
     * @param string   $char_list           <p>Additional chars for the definition of "words".</p>
8009
     * @param bool     $remove_empty_values <p>Remove empty values.</p>
8010
     * @param int|null $remove_short_values <p>The min. string length or null to disable</p>
8011
     *
8012
     * @return string[]
8013
     */
8014
    public static function str_to_words(
8015
        string $str,
8016
        string $char_list = '',
8017
        bool $remove_empty_values = false,
8018
        int $remove_short_values = null
8019
    ): array {
8020 13
        if ($str === '') {
8021 4
            return $remove_empty_values === true ? [] : [''];
8022
        }
8023
8024 13
        $char_list = self::rxClass($char_list, '\pL');
8025
8026 13
        $return = \preg_split("/({$char_list}+(?:[\p{Pd}’']{$char_list}+)*)/u", $str, -1, \PREG_SPLIT_DELIM_CAPTURE);
8027 13
        if ($return === false) {
8028
            return $remove_empty_values === true ? [] : [''];
8029
        }
8030
8031
        if (
8032 13
            $remove_short_values === null
8033
            &&
8034 13
            $remove_empty_values === false
8035
        ) {
8036 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...
8037
        }
8038
8039 2
        $tmp_return = self::reduce_string_array(
8040 2
            $return,
8041 2
            $remove_empty_values,
8042 2
            $remove_short_values
8043
        );
8044
8045 2
        foreach ($tmp_return as &$item) {
8046 2
            $item = (string) $item;
8047
        }
8048
8049 2
        return $tmp_return;
8050
    }
8051
8052
    /**
8053
     * alias for "UTF8::to_ascii()"
8054
     *
8055
     * @param string $str
8056
     * @param string $unknown
8057
     * @param bool   $strict
8058
     *
8059
     * @return string
8060
     *
8061
     * @see UTF8::to_ascii()
8062
     * @deprecated <p>please use "UTF8::to_ascii()"</p>
8063
     */
8064
    public static function str_transliterate(
8065
        string $str,
8066
        string $unknown = '?',
8067
        bool $strict = false
8068
    ): string {
8069 7
        return self::to_ascii($str, $unknown, $strict);
8070
    }
8071
8072
    /**
8073
     * Truncates the string to a given length. If $substring is provided, and
8074
     * truncating occurs, the string is further truncated so that the substring
8075
     * may be appended without exceeding the desired length.
8076
     *
8077
     * @param string $str
8078
     * @param int    $length    <p>Desired length of the truncated string.</p>
8079
     * @param string $substring [optional] <p>The substring to append if it can fit. Default: ''</p>
8080
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
8081
     *
8082
     * @return string string after truncating
8083
     */
8084
    public static function str_truncate(
8085
        string $str,
8086
        int $length,
8087
        string $substring = '',
8088
        string $encoding = 'UTF-8'
8089
    ): string {
8090 22
        if ($str === '') {
8091
            return '';
8092
        }
8093
8094 22
        if ($encoding === 'UTF-8') {
8095 10
            if ($length >= (int) \mb_strlen($str)) {
8096 2
                return $str;
8097
            }
8098
8099 8
            if ($substring !== '') {
8100 4
                $length -= (int) \mb_strlen($substring);
8101
8102
                /** @noinspection UnnecessaryCastingInspection */
8103 4
                return (string) \mb_substr($str, 0, $length) . $substring;
8104
            }
8105
8106
            /** @noinspection UnnecessaryCastingInspection */
8107 4
            return (string) \mb_substr($str, 0, $length);
8108
        }
8109
8110 12
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
8111
8112 12
        if ($length >= (int) self::strlen($str, $encoding)) {
8113 2
            return $str;
8114
        }
8115
8116 10
        if ($substring !== '') {
8117 6
            $length -= (int) self::strlen($substring, $encoding);
8118
        }
8119
8120
        return (
8121 10
               (string) self::substr(
8122 10
                   $str,
8123 10
                   0,
8124 10
                   $length,
8125 10
                   $encoding
8126
               )
8127 10
               ) . $substring;
8128
    }
8129
8130
    /**
8131
     * Truncates the string to a given length, while ensuring that it does not
8132
     * split words. If $substring is provided, and truncating occurs, the
8133
     * string is further truncated so that the substring may be appended without
8134
     * exceeding the desired length.
8135
     *
8136
     * @param string $str
8137
     * @param int    $length                                 <p>Desired length of the truncated string.</p>
8138
     * @param string $substring                              [optional] <p>The substring to append if it can fit. Default:
8139
     *                                                       ''</p>
8140
     * @param string $encoding                               [optional] <p>Default: 'UTF-8'</p>
8141
     * @param bool   $ignore_do_not_split_words_for_one_word [optional] <p>Default: false</p>
8142
     *
8143
     * @return string string after truncating
8144
     */
8145
    public static function str_truncate_safe(
8146
        string $str,
8147
        int $length,
8148
        string $substring = '',
8149
        string $encoding = 'UTF-8',
8150
        bool $ignore_do_not_split_words_for_one_word = false
8151
    ): string {
8152 47
        if ($str === '' || $length <= 0) {
8153 1
            return $substring;
8154
        }
8155
8156 47
        if ($encoding === 'UTF-8') {
8157 21
            if ($length >= (int) \mb_strlen($str)) {
8158 5
                return $str;
8159
            }
8160
8161
            // need to further trim the string so we can append the substring
8162 17
            $length -= (int) \mb_strlen($substring);
8163 17
            if ($length <= 0) {
8164 1
                return $substring;
8165
            }
8166
8167
            /** @var false|string $truncated - needed for PhpStan (stubs error) */
8168 17
            $truncated = \mb_substr($str, 0, $length);
8169 17
            if ($truncated === false) {
8170
                return '';
8171
            }
8172
8173
            // if the last word was truncated
8174 17
            $space_position = \mb_strpos($str, ' ', $length - 1);
8175 17
            if ($space_position !== $length) {
8176
                // find pos of the last occurrence of a space, get up to that
8177 13
                $last_position = \mb_strrpos($truncated, ' ', 0);
8178
8179
                if (
8180 13
                    $last_position !== false
8181
                    ||
8182 13
                    ($space_position !== false && $ignore_do_not_split_words_for_one_word === false)
8183
                ) {
8184 17
                    $truncated = (string) \mb_substr($truncated, 0, (int) $last_position);
8185
                }
8186
            }
8187
        } else {
8188 26
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
8189
8190 26
            if ($length >= (int) self::strlen($str, $encoding)) {
8191 4
                return $str;
8192
            }
8193
8194
            // need to further trim the string so we can append the substring
8195 22
            $length -= (int) self::strlen($substring, $encoding);
8196 22
            if ($length <= 0) {
8197
                return $substring;
8198
            }
8199
8200 22
            $truncated = self::substr($str, 0, $length, $encoding);
8201
8202 22
            if ($truncated === false) {
8203
                return '';
8204
            }
8205
8206
            // if the last word was truncated
8207 22
            $space_position = self::strpos($str, ' ', $length - 1, $encoding);
8208 22
            if ($space_position !== $length) {
8209
                // find pos of the last occurrence of a space, get up to that
8210 12
                $last_position = self::strrpos($truncated, ' ', 0, $encoding);
8211
8212
                if (
8213 12
                    $last_position !== false
8214
                    ||
8215 12
                    ($space_position !== false && $ignore_do_not_split_words_for_one_word === false)
8216
                ) {
8217 9
                    $truncated = (string) self::substr($truncated, 0, (int) $last_position, $encoding);
8218
                }
8219
            }
8220
        }
8221
8222 39
        return $truncated . $substring;
8223
    }
8224
8225
    /**
8226
     * Returns a lowercase and trimmed string separated by underscores.
8227
     * Underscores are inserted before uppercase characters (with the exception
8228
     * of the first character of the string), and in place of spaces as well as
8229
     * dashes.
8230
     *
8231
     * @param string $str
8232
     *
8233
     * @return string the underscored string
8234
     */
8235
    public static function str_underscored(string $str): string
8236
    {
8237 16
        return self::str_delimit($str, '_');
8238
    }
8239
8240
    /**
8241
     * Returns an UpperCamelCase version of the supplied string. It trims
8242
     * surrounding spaces, capitalizes letters following digits, spaces, dashes
8243
     * and underscores, and removes spaces, dashes, underscores.
8244
     *
8245
     * @param string      $str                           <p>The input string.</p>
8246
     * @param string      $encoding                      [optional] <p>Default: 'UTF-8'</p>
8247
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
8248
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
8249
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
8250
     *
8251
     * @return string string in UpperCamelCase
8252
     */
8253
    public static function str_upper_camelize(
8254
        string $str,
8255
        string $encoding = 'UTF-8',
8256
        bool $clean_utf8 = false,
8257
        string $lang = null,
8258
        bool $try_to_keep_the_string_length = false
8259
    ): string {
8260 13
        return self::ucfirst(self::str_camelize($str, $encoding), $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
8261
    }
8262
8263
    /**
8264
     * alias for "UTF8::ucfirst()"
8265
     *
8266
     * @param string      $str
8267
     * @param string      $encoding
8268
     * @param bool        $clean_utf8
8269
     * @param string|null $lang
8270
     * @param bool        $try_to_keep_the_string_length
8271
     *
8272
     * @return string
8273
     *
8274
     * @see UTF8::ucfirst()
8275
     * @deprecated <p>please use "UTF8::ucfirst()"</p>
8276
     */
8277
    public static function str_upper_first(
8278
        string $str,
8279
        string $encoding = 'UTF-8',
8280
        bool $clean_utf8 = false,
8281
        string $lang = null,
8282
        bool $try_to_keep_the_string_length = false
8283
    ): string {
8284 5
        return self::ucfirst(
8285 5
            $str,
8286 5
            $encoding,
8287 5
            $clean_utf8,
8288 5
            $lang,
8289 5
            $try_to_keep_the_string_length
8290
        );
8291
    }
8292
8293
    /**
8294
     * Get the number of words in a specific string.
8295
     *
8296
     * @param string $str       <p>The input string.</p>
8297
     * @param int    $format    [optional] <p>
8298
     *                          <strong>0</strong> => return a number of words (default)<br>
8299
     *                          <strong>1</strong> => return an array of words<br>
8300
     *                          <strong>2</strong> => return an array of words with word-offset as key
8301
     *                          </p>
8302
     * @param string $char_list [optional] <p>Additional chars that contains to words and do not start a new word.</p>
8303
     *
8304
     * @return int|string[] The number of words in the string
8305
     */
8306
    public static function str_word_count(string $str, int $format = 0, string $char_list = '')
8307
    {
8308 2
        $str_parts = self::str_to_words($str, $char_list);
8309
8310 2
        $len = \count($str_parts);
8311
8312 2
        if ($format === 1) {
8313 2
            $number_of_words = [];
8314 2
            for ($i = 1; $i < $len; $i += 2) {
8315 2
                $number_of_words[] = $str_parts[$i];
8316
            }
8317 2
        } elseif ($format === 2) {
8318 2
            $number_of_words = [];
8319 2
            $offset = (int) self::strlen($str_parts[0]);
8320 2
            for ($i = 1; $i < $len; $i += 2) {
8321 2
                $number_of_words[$offset] = $str_parts[$i];
8322 2
                $offset += (int) self::strlen($str_parts[$i]) + (int) self::strlen($str_parts[$i + 1]);
8323
            }
8324
        } else {
8325 2
            $number_of_words = (int) (($len - 1) / 2);
8326
        }
8327
8328 2
        return $number_of_words;
8329
    }
8330
8331
    /**
8332
     * Case-insensitive string comparison.
8333
     *
8334
     * INFO: Case-insensitive version of UTF8::strcmp()
8335
     *
8336
     * @param string $str1     <p>The first string.</p>
8337
     * @param string $str2     <p>The second string.</p>
8338
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
8339
     *
8340
     * @return int
8341
     *             <strong>&lt; 0</strong> if str1 is less than str2;<br>
8342
     *             <strong>&gt; 0</strong> if str1 is greater than str2,<br>
8343
     *             <strong>0</strong> if they are equal
8344
     */
8345
    public static function strcasecmp(
8346
        string $str1,
8347
        string $str2,
8348
        string $encoding = 'UTF-8'
8349
    ): int {
8350 23
        return self::strcmp(
8351 23
            self::strtocasefold(
8352 23
                $str1,
8353 23
                true,
8354 23
                false,
8355 23
                $encoding,
8356 23
                null,
8357 23
                false
8358
            ),
8359 23
            self::strtocasefold(
8360 23
                $str2,
8361 23
                true,
8362 23
                false,
8363 23
                $encoding,
8364 23
                null,
8365 23
                false
8366
            )
8367
        );
8368
    }
8369
8370
    /**
8371
     * alias for "UTF8::strstr()"
8372
     *
8373
     * @param string $haystack
8374
     * @param string $needle
8375
     * @param bool   $before_needle
8376
     * @param string $encoding
8377
     * @param bool   $clean_utf8
8378
     *
8379
     * @return false|string
8380
     *
8381
     * @see UTF8::strstr()
8382
     * @deprecated <p>please use "UTF8::strstr()"</p>
8383
     */
8384
    public static function strchr(
8385
        string $haystack,
8386
        string $needle,
8387
        bool $before_needle = false,
8388
        string $encoding = 'UTF-8',
8389
        bool $clean_utf8 = false
8390
    ) {
8391 2
        return self::strstr(
8392 2
            $haystack,
8393 2
            $needle,
8394 2
            $before_needle,
8395 2
            $encoding,
8396 2
            $clean_utf8
8397
        );
8398
    }
8399
8400
    /**
8401
     * Case-sensitive string comparison.
8402
     *
8403
     * @param string $str1 <p>The first string.</p>
8404
     * @param string $str2 <p>The second string.</p>
8405
     *
8406
     * @return int
8407
     *             <strong>&lt; 0</strong> if str1 is less than str2<br>
8408
     *             <strong>&gt; 0</strong> if str1 is greater than str2<br>
8409
     *             <strong>0</strong> if they are equal
8410
     */
8411
    public static function strcmp(string $str1, string $str2): int
8412
    {
8413 29
        if ($str1 === $str2) {
8414 21
            return 0;
8415
        }
8416
8417 24
        return \strcmp(
8418 24
            \Normalizer::normalize($str1, \Normalizer::NFD),
8419 24
            \Normalizer::normalize($str2, \Normalizer::NFD)
8420
        );
8421
    }
8422
8423
    /**
8424
     * Find length of initial segment not matching mask.
8425
     *
8426
     * @param string $str
8427
     * @param string $char_list
8428
     * @param int    $offset
8429
     * @param int    $length
8430
     * @param string $encoding  [optional] <p>Set the charset for e.g. "mb_" function</p>
8431
     *
8432
     * @return int
8433
     */
8434
    public static function strcspn(
8435
        string $str,
8436
        string $char_list,
8437
        int $offset = null,
8438
        int $length = null,
8439
        string $encoding = 'UTF-8'
8440
    ): int {
8441 12
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
8442
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
8443
        }
8444
8445 12
        if ($char_list === '') {
8446 2
            return (int) self::strlen($str, $encoding);
8447
        }
8448
8449 11
        if ($offset !== null || $length !== null) {
8450 3
            if ($encoding === 'UTF-8') {
8451 3
                if ($length === null) {
8452
                    /** @noinspection UnnecessaryCastingInspection */
8453 2
                    $str_tmp = \mb_substr($str, (int) $offset);
8454
                } else {
8455
                    /** @noinspection UnnecessaryCastingInspection */
8456 3
                    $str_tmp = \mb_substr($str, (int) $offset, $length);
8457
                }
8458
            } else {
8459
                /** @noinspection UnnecessaryCastingInspection */
8460
                $str_tmp = self::substr($str, (int) $offset, $length, $encoding);
8461
            }
8462
8463 3
            if ($str_tmp === false) {
8464
                return 0;
8465
            }
8466
8467
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
8468 3
            $str = $str_tmp;
8469
        }
8470
8471 11
        if ($str === '') {
8472 2
            return 0;
8473
        }
8474
8475 10
        $matches = [];
8476 10
        if (\preg_match('/^(.*?)' . self::rxClass($char_list) . '/us', $str, $matches)) {
8477 9
            $return = self::strlen($matches[1], $encoding);
8478 9
            if ($return === false) {
8479
                return 0;
8480
            }
8481
8482 9
            return $return;
8483
        }
8484
8485 2
        return (int) self::strlen($str, $encoding);
8486
    }
8487
8488
    /**
8489
     * alias for "UTF8::stristr()"
8490
     *
8491
     * @param string $haystack
8492
     * @param string $needle
8493
     * @param bool   $before_needle
8494
     * @param string $encoding
8495
     * @param bool   $clean_utf8
8496
     *
8497
     * @return false|string
8498
     *
8499
     * @see UTF8::stristr()
8500
     * @deprecated <p>please use "UTF8::stristr()"</p>
8501
     */
8502
    public static function strichr(
8503
        string $haystack,
8504
        string $needle,
8505
        bool $before_needle = false,
8506
        string $encoding = 'UTF-8',
8507
        bool $clean_utf8 = false
8508
    ) {
8509 1
        return self::stristr(
8510 1
            $haystack,
8511 1
            $needle,
8512 1
            $before_needle,
8513 1
            $encoding,
8514 1
            $clean_utf8
8515
        );
8516
    }
8517
8518
    /**
8519
     * Create a UTF-8 string from code points.
8520
     *
8521
     * INFO: opposite to UTF8::codepoints()
8522
     *
8523
     * @param array $array <p>Integer or Hexadecimal codepoints.</p>
8524
     *
8525
     * @return string UTF-8 encoded string
8526
     */
8527
    public static function string(array $array): string
8528
    {
8529 4
        return \implode(
8530 4
            '',
8531 4
            \array_map(
8532
                [
8533 4
                    self::class,
8534
                    'chr',
8535
                ],
8536 4
                $array
8537
            )
8538
        );
8539
    }
8540
8541
    /**
8542
     * Checks if string starts with "BOM" (Byte Order Mark Character) character.
8543
     *
8544
     * @param string $str <p>The input string.</p>
8545
     *
8546
     * @return bool
8547
     *              <strong>true</strong> if the string has BOM at the start,<br>
8548
     *              <strong>false</strong> otherwise
8549
     */
8550
    public static function string_has_bom(string $str): bool
8551
    {
8552
        /** @noinspection PhpUnusedLocalVariableInspection */
8553 6
        foreach (self::$BOM as $bom_string => &$bom_byte_length) {
8554 6
            if (\strpos($str, $bom_string) === 0) {
8555 6
                return true;
8556
            }
8557
        }
8558
8559 6
        return false;
8560
    }
8561
8562
    /**
8563
     * Strip HTML and PHP tags from a string + clean invalid UTF-8.
8564
     *
8565
     * @see http://php.net/manual/en/function.strip-tags.php
8566
     *
8567
     * @param string $str            <p>
8568
     *                               The input string.
8569
     *                               </p>
8570
     * @param string $allowable_tags [optional] <p>
8571
     *                               You can use the optional second parameter to specify tags which should
8572
     *                               not be stripped.
8573
     *                               </p>
8574
     *                               <p>
8575
     *                               HTML comments and PHP tags are also stripped. This is hardcoded and
8576
     *                               can not be changed with allowable_tags.
8577
     *                               </p>
8578
     * @param bool   $clean_utf8     [optional] <p>Remove non UTF-8 chars from the string.</p>
8579
     *
8580
     * @return string
8581
     *                <p>The stripped string.</p>
8582
     */
8583
    public static function strip_tags(
8584
        string $str,
8585
        string $allowable_tags = null,
8586
        bool $clean_utf8 = false
8587
    ): string {
8588 4
        if ($str === '') {
8589 1
            return '';
8590
        }
8591
8592 4
        if ($clean_utf8 === true) {
8593 2
            $str = self::clean($str);
8594
        }
8595
8596 4
        if ($allowable_tags === null) {
8597 4
            return \strip_tags($str);
8598
        }
8599
8600 2
        return \strip_tags($str, $allowable_tags);
8601
    }
8602
8603
    /**
8604
     * Strip all whitespace characters. This includes tabs and newline
8605
     * characters, as well as multibyte whitespace such as the thin space
8606
     * and ideographic space.
8607
     *
8608
     * @param string $str
8609
     *
8610
     * @return string
8611
     */
8612
    public static function strip_whitespace(string $str): string
8613
    {
8614 36
        if ($str === '') {
8615 3
            return '';
8616
        }
8617
8618 33
        return (string) \preg_replace('/[[:space:]]+/u', '', $str);
8619
    }
8620
8621
    /**
8622
     * Find the position of the first occurrence of a substring in a string, case-insensitive.
8623
     *
8624
     * @see http://php.net/manual/en/function.mb-stripos.php
8625
     *
8626
     * @param string $haystack   <p>The string from which to get the position of the first occurrence of needle.</p>
8627
     * @param string $needle     <p>The string to find in haystack.</p>
8628
     * @param int    $offset     [optional] <p>The position in haystack to start searching.</p>
8629
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
8630
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
8631
     *
8632
     * @return false|int
8633
     *                   Return the <strong>(int)</strong> numeric position of the first occurrence of needle in the
8634
     *                   haystack string,<br> or <strong>false</strong> if needle is not found
8635
     */
8636
    public static function stripos(
8637
        string $haystack,
8638
        string $needle,
8639
        int $offset = 0,
8640
        $encoding = 'UTF-8',
8641
        bool $clean_utf8 = false
8642
    ) {
8643 24
        if ($haystack === '' || $needle === '') {
8644 5
            return false;
8645
        }
8646
8647 23
        if ($clean_utf8 === true) {
8648
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
8649
            // if invalid characters are found in $haystack before $needle
8650 1
            $haystack = self::clean($haystack);
8651 1
            $needle = self::clean($needle);
8652
        }
8653
8654 23
        if (self::$SUPPORT['mbstring'] === true) {
8655 23
            if ($encoding === 'UTF-8') {
8656 23
                return \mb_stripos($haystack, $needle, $offset);
8657
            }
8658
8659 3
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
8660
8661 3
            return \mb_stripos($haystack, $needle, $offset, $encoding);
8662
        }
8663
8664 2
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
8665
8666
        if (
8667 2
            $encoding === 'UTF-8' // INFO: "grapheme_stripos()" can't handle other encodings
8668
            &&
8669 2
            $offset >= 0 // grapheme_stripos() can't handle negative offset
8670
            &&
8671 2
            self::$SUPPORT['intl'] === true
8672
        ) {
8673
            $return_tmp = \grapheme_stripos($haystack, $needle, $offset);
8674
            if ($return_tmp !== false) {
8675
                return $return_tmp;
8676
            }
8677
        }
8678
8679
        //
8680
        // fallback for ascii only
8681
        //
8682
8683 2
        if (ASCII::is_ascii($haystack . $needle)) {
8684
            return \stripos($haystack, $needle, $offset);
8685
        }
8686
8687
        //
8688
        // fallback via vanilla php
8689
        //
8690
8691 2
        $haystack = self::strtocasefold($haystack, true, false, $encoding, null, false);
8692 2
        $needle = self::strtocasefold($needle, true, false, $encoding, null, false);
8693
8694 2
        return self::strpos($haystack, $needle, $offset, $encoding);
8695
    }
8696
8697
    /**
8698
     * Returns all of haystack starting from and including the first occurrence of needle to the end.
8699
     *
8700
     * @param string $haystack      <p>The input string. Must be valid UTF-8.</p>
8701
     * @param string $needle        <p>The string to look for. Must be valid UTF-8.</p>
8702
     * @param bool   $before_needle [optional] <p>
8703
     *                              If <b>TRUE</b>, it returns the part of the
8704
     *                              haystack before the first occurrence of the needle (excluding the needle).
8705
     *                              </p>
8706
     * @param string $encoding      [optional] <p>Set the charset for e.g. "mb_" function</p>
8707
     * @param bool   $clean_utf8    [optional] <p>Remove non UTF-8 chars from the string.</p>
8708
     *
8709
     * @return false|string
8710
     *                      <p>A sub-string,<br>or <strong>false</strong> if needle is not found.</p>
8711
     */
8712
    public static function stristr(
8713
        string $haystack,
8714
        string $needle,
8715
        bool $before_needle = false,
8716
        string $encoding = 'UTF-8',
8717
        bool $clean_utf8 = false
8718
    ) {
8719 12
        if ($haystack === '' || $needle === '') {
8720 3
            return false;
8721
        }
8722
8723 9
        if ($clean_utf8 === true) {
8724
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
8725
            // if invalid characters are found in $haystack before $needle
8726 1
            $needle = self::clean($needle);
8727 1
            $haystack = self::clean($haystack);
8728
        }
8729
8730 9
        if (!$needle) {
8731
            return $haystack;
8732
        }
8733
8734 9
        if (self::$SUPPORT['mbstring'] === true) {
8735 9
            if ($encoding === 'UTF-8') {
8736 9
                return \mb_stristr($haystack, $needle, $before_needle);
8737
            }
8738
8739 1
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
8740
8741 1
            return \mb_stristr($haystack, $needle, $before_needle, $encoding);
8742
        }
8743
8744
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
8745
8746
        if (
8747
            $encoding !== 'UTF-8'
8748
            &&
8749
            self::$SUPPORT['mbstring'] === false
8750
        ) {
8751
            \trigger_error('UTF8::stristr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
8752
        }
8753
8754
        if (
8755
            $encoding === 'UTF-8' // INFO: "grapheme_stristr()" can't handle other encodings
8756
            &&
8757
            self::$SUPPORT['intl'] === true
8758
        ) {
8759
            $return_tmp = \grapheme_stristr($haystack, $needle, $before_needle);
8760
            if ($return_tmp !== false) {
8761
                return $return_tmp;
8762
            }
8763
        }
8764
8765
        if (ASCII::is_ascii($needle . $haystack)) {
8766
            return \stristr($haystack, $needle, $before_needle);
8767
        }
8768
8769
        \preg_match('/^(.*?)' . \preg_quote($needle, '/') . '/usi', $haystack, $match);
8770
8771
        if (!isset($match[1])) {
8772
            return false;
8773
        }
8774
8775
        if ($before_needle) {
8776
            return $match[1];
8777
        }
8778
8779
        return self::substr($haystack, (int) self::strlen($match[1], $encoding), null, $encoding);
8780
    }
8781
8782
    /**
8783
     * Get the string length, not the byte-length!
8784
     *
8785
     * @see http://php.net/manual/en/function.mb-strlen.php
8786
     *
8787
     * @param string $str        <p>The string being checked for length.</p>
8788
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
8789
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
8790
     *
8791
     * @return false|int
8792
     *                   <p>
8793
     *                   The number <strong>(int)</strong> of characters in the string $str having character encoding
8794
     *                   $encoding.
8795
     *                   (One multi-byte character counted as +1).
8796
     *                   <br>
8797
     *                   Can return <strong>false</strong>, if e.g. mbstring is not installed and we process invalid
8798
     *                   chars.
8799
     *                   </p>
8800
     */
8801
    public static function strlen(
8802
        string $str,
8803
        string $encoding = 'UTF-8',
8804
        bool $clean_utf8 = false
8805
    ) {
8806 173
        if ($str === '') {
8807 21
            return 0;
8808
        }
8809
8810 171
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
8811 12
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
8812
        }
8813
8814 171
        if ($clean_utf8 === true) {
8815
            // "mb_strlen" and "\iconv_strlen" returns wrong length,
8816
            // if invalid characters are found in $str
8817 4
            $str = self::clean($str);
8818
        }
8819
8820
        //
8821
        // fallback via mbstring
8822
        //
8823
8824 171
        if (self::$SUPPORT['mbstring'] === true) {
8825 165
            if ($encoding === 'UTF-8') {
8826 165
                return \mb_strlen($str);
8827
            }
8828
8829 4
            return \mb_strlen($str, $encoding);
8830
        }
8831
8832
        //
8833
        // fallback for binary || ascii only
8834
        //
8835
8836
        if (
8837 8
            $encoding === 'CP850'
8838
            ||
8839 8
            $encoding === 'ASCII'
8840
        ) {
8841
            return \strlen($str);
8842
        }
8843
8844
        if (
8845 8
            $encoding !== 'UTF-8'
8846
            &&
8847 8
            self::$SUPPORT['mbstring'] === false
8848
            &&
8849 8
            self::$SUPPORT['iconv'] === false
8850
        ) {
8851 2
            \trigger_error('UTF8::strlen() without mbstring / iconv cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
8852
        }
8853
8854
        //
8855
        // fallback via iconv
8856
        //
8857
8858 8
        if (self::$SUPPORT['iconv'] === true) {
8859
            $return_tmp = \iconv_strlen($str, $encoding);
8860
            if ($return_tmp !== false) {
8861
                return $return_tmp;
8862
            }
8863
        }
8864
8865
        //
8866
        // fallback via intl
8867
        //
8868
8869
        if (
8870 8
            $encoding === 'UTF-8' // INFO: "grapheme_strlen()" can't handle other encodings
8871
            &&
8872 8
            self::$SUPPORT['intl'] === true
8873
        ) {
8874
            $return_tmp = \grapheme_strlen($str);
8875
            if ($return_tmp !== null) {
8876
                return $return_tmp;
8877
            }
8878
        }
8879
8880
        //
8881
        // fallback for ascii only
8882
        //
8883
8884 8
        if (ASCII::is_ascii($str)) {
8885 4
            return \strlen($str);
8886
        }
8887
8888
        //
8889
        // fallback via vanilla php
8890
        //
8891
8892 8
        \preg_match_all('/./us', $str, $parts);
8893
8894 8
        $return_tmp = \count($parts[0]);
8895 8
        if ($return_tmp === 0) {
8896
            return false;
8897
        }
8898
8899 8
        return $return_tmp;
8900
    }
8901
8902
    /**
8903
     * Get string length in byte.
8904
     *
8905
     * @param string $str
8906
     *
8907
     * @return int
8908
     */
8909
    public static function strlen_in_byte(string $str): int
8910
    {
8911
        if ($str === '') {
8912
            return 0;
8913
        }
8914
8915
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
8916
            // "mb_" is available if overload is used, so use it ...
8917
            return \mb_strlen($str, 'CP850'); // 8-BIT
8918
        }
8919
8920
        return \strlen($str);
8921
    }
8922
8923
    /**
8924
     * Case-insensitive string comparisons using a "natural order" algorithm.
8925
     *
8926
     * INFO: natural order version of UTF8::strcasecmp()
8927
     *
8928
     * @param string $str1     <p>The first string.</p>
8929
     * @param string $str2     <p>The second string.</p>
8930
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
8931
     *
8932
     * @return int
8933
     *             <strong>&lt; 0</strong> if str1 is less than str2<br>
8934
     *             <strong>&gt; 0</strong> if str1 is greater than str2<br>
8935
     *             <strong>0</strong> if they are equal
8936
     */
8937
    public static function strnatcasecmp(string $str1, string $str2, string $encoding = 'UTF-8'): int
8938
    {
8939 2
        return self::strnatcmp(
8940 2
            self::strtocasefold($str1, true, false, $encoding, null, false),
8941 2
            self::strtocasefold($str2, true, false, $encoding, null, false)
8942
        );
8943
    }
8944
8945
    /**
8946
     * String comparisons using a "natural order" algorithm
8947
     *
8948
     * INFO: natural order version of UTF8::strcmp()
8949
     *
8950
     * @see http://php.net/manual/en/function.strnatcmp.php
8951
     *
8952
     * @param string $str1 <p>The first string.</p>
8953
     * @param string $str2 <p>The second string.</p>
8954
     *
8955
     * @return int
8956
     *             <strong>&lt; 0</strong> if str1 is less than str2;<br>
8957
     *             <strong>&gt; 0</strong> if str1 is greater than str2;<br>
8958
     *             <strong>0</strong> if they are equal
8959
     */
8960
    public static function strnatcmp(string $str1, string $str2): int
8961
    {
8962 4
        if ($str1 === $str2) {
8963 4
            return 0;
8964
        }
8965
8966 4
        return \strnatcmp(
8967 4
            (string) self::strtonatfold($str1),
8968 4
            (string) self::strtonatfold($str2)
8969
        );
8970
    }
8971
8972
    /**
8973
     * Case-insensitive string comparison of the first n characters.
8974
     *
8975
     * @see http://php.net/manual/en/function.strncasecmp.php
8976
     *
8977
     * @param string $str1     <p>The first string.</p>
8978
     * @param string $str2     <p>The second string.</p>
8979
     * @param int    $len      <p>The length of strings to be used in the comparison.</p>
8980
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
8981
     *
8982
     * @return int
8983
     *             <strong>&lt; 0</strong> if <i>str1</i> is less than <i>str2</i>;<br>
8984
     *             <strong>&gt; 0</strong> if <i>str1</i> is greater than <i>str2</i>;<br>
8985
     *             <strong>0</strong> if they are equal
8986
     */
8987
    public static function strncasecmp(
8988
        string $str1,
8989
        string $str2,
8990
        int $len,
8991
        string $encoding = 'UTF-8'
8992
    ): int {
8993 2
        return self::strncmp(
8994 2
            self::strtocasefold($str1, true, false, $encoding, null, false),
8995 2
            self::strtocasefold($str2, true, false, $encoding, null, false),
8996 2
            $len
8997
        );
8998
    }
8999
9000
    /**
9001
     * String comparison of the first n characters.
9002
     *
9003
     * @see http://php.net/manual/en/function.strncmp.php
9004
     *
9005
     * @param string $str1     <p>The first string.</p>
9006
     * @param string $str2     <p>The second string.</p>
9007
     * @param int    $len      <p>Number of characters to use in the comparison.</p>
9008
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
9009
     *
9010
     * @return int
9011
     *             <strong>&lt; 0</strong> if <i>str1</i> is less than <i>str2</i>;<br>
9012
     *             <strong>&gt; 0</strong> if <i>str1</i> is greater than <i>str2</i>;<br>
9013
     *             <strong>0</strong> if they are equal
9014
     */
9015
    public static function strncmp(
9016
        string $str1,
9017
        string $str2,
9018
        int $len,
9019
        string $encoding = 'UTF-8'
9020
    ): int {
9021 4
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9022
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9023
        }
9024
9025 4
        if ($encoding === 'UTF-8') {
9026 4
            $str1 = (string) \mb_substr($str1, 0, $len);
9027 4
            $str2 = (string) \mb_substr($str2, 0, $len);
9028
        } else {
9029
            $str1 = (string) self::substr($str1, 0, $len, $encoding);
9030
            $str2 = (string) self::substr($str2, 0, $len, $encoding);
9031
        }
9032
9033 4
        return self::strcmp($str1, $str2);
9034
    }
9035
9036
    /**
9037
     * Search a string for any of a set of characters.
9038
     *
9039
     * @see http://php.net/manual/en/function.strpbrk.php
9040
     *
9041
     * @param string $haystack  <p>The string where char_list is looked for.</p>
9042
     * @param string $char_list <p>This parameter is case-sensitive.</p>
9043
     *
9044
     * @return false|string string starting from the character found, or false if it is not found
9045
     */
9046
    public static function strpbrk(string $haystack, string $char_list)
9047
    {
9048 2
        if ($haystack === '' || $char_list === '') {
9049 2
            return false;
9050
        }
9051
9052 2
        if (\preg_match('/' . self::rxClass($char_list) . '/us', $haystack, $m)) {
9053 2
            return \substr($haystack, (int) \strpos($haystack, $m[0]));
9054
        }
9055
9056 2
        return false;
9057
    }
9058
9059
    /**
9060
     * Find the position of the first occurrence of a substring in a string.
9061
     *
9062
     * @see http://php.net/manual/en/function.mb-strpos.php
9063
     *
9064
     * @param string     $haystack   <p>The string from which to get the position of the first occurrence of needle.</p>
9065
     * @param int|string $needle     <p>The string to find in haystack.<br>Or a code point as int.</p>
9066
     * @param int        $offset     [optional] <p>The search offset. If it is not specified, 0 is used.</p>
9067
     * @param string     $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
9068
     * @param bool       $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
9069
     *
9070
     * @return false|int
9071
     *                   The <strong>(int)</strong> numeric position of the first occurrence of needle in the haystack
9072
     *                   string.<br> If needle is not found it returns false.
9073
     */
9074
    public static function strpos(
9075
        string $haystack,
9076
        $needle,
9077
        int $offset = 0,
9078
        $encoding = 'UTF-8',
9079
        bool $clean_utf8 = false
9080
    ) {
9081 53
        if ($haystack === '') {
9082 4
            return false;
9083
        }
9084
9085
        // iconv and mbstring do not support integer $needle
9086 52
        if ((int) $needle === $needle) {
9087
            $needle = (string) self::chr($needle);
9088
        }
9089 52
        $needle = (string) $needle;
9090
9091 52
        if ($needle === '') {
9092 2
            return false;
9093
        }
9094
9095 52
        if ($clean_utf8 === true) {
9096
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
9097
            // if invalid characters are found in $haystack before $needle
9098 3
            $needle = self::clean($needle);
9099 3
            $haystack = self::clean($haystack);
9100
        }
9101
9102 52
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9103 11
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9104
        }
9105
9106
        //
9107
        // fallback via mbstring
9108
        //
9109
9110 52
        if (self::$SUPPORT['mbstring'] === true) {
9111 50
            if ($encoding === 'UTF-8') {
9112 50
                return \mb_strpos($haystack, $needle, $offset);
9113
            }
9114
9115 2
            return \mb_strpos($haystack, $needle, $offset, $encoding);
9116
        }
9117
9118
        //
9119
        // fallback for binary || ascii only
9120
        //
9121
        if (
9122 4
            $encoding === 'CP850'
9123
            ||
9124 4
            $encoding === 'ASCII'
9125
        ) {
9126 2
            return \strpos($haystack, $needle, $offset);
9127
        }
9128
9129
        if (
9130 4
            $encoding !== 'UTF-8'
9131
            &&
9132 4
            self::$SUPPORT['iconv'] === false
9133
            &&
9134 4
            self::$SUPPORT['mbstring'] === false
9135
        ) {
9136 2
            \trigger_error('UTF8::strpos() without mbstring / iconv cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
9137
        }
9138
9139
        //
9140
        // fallback via intl
9141
        //
9142
9143
        if (
9144 4
            $encoding === 'UTF-8' // INFO: "grapheme_strpos()" can't handle other encodings
9145
            &&
9146 4
            $offset >= 0 // grapheme_strpos() can't handle negative offset
9147
            &&
9148 4
            self::$SUPPORT['intl'] === true
9149
        ) {
9150
            $return_tmp = \grapheme_strpos($haystack, $needle, $offset);
9151
            if ($return_tmp !== false) {
9152
                return $return_tmp;
9153
            }
9154
        }
9155
9156
        //
9157
        // fallback via iconv
9158
        //
9159
9160
        if (
9161 4
            $offset >= 0 // iconv_strpos() can't handle negative offset
9162
            &&
9163 4
            self::$SUPPORT['iconv'] === true
9164
        ) {
9165
            // ignore invalid negative offset to keep compatibility
9166
            // with php < 5.5.35, < 5.6.21, < 7.0.6
9167
            $return_tmp = \iconv_strpos($haystack, $needle, $offset > 0 ? $offset : 0, $encoding);
9168
            if ($return_tmp !== false) {
9169
                return $return_tmp;
9170
            }
9171
        }
9172
9173
        //
9174
        // fallback for ascii only
9175
        //
9176
9177 4
        if (ASCII::is_ascii($haystack . $needle)) {
9178 2
            return \strpos($haystack, $needle, $offset);
9179
        }
9180
9181
        //
9182
        // fallback via vanilla php
9183
        //
9184
9185 4
        $haystack_tmp = self::substr($haystack, $offset, null, $encoding);
9186 4
        if ($haystack_tmp === false) {
9187
            $haystack_tmp = '';
9188
        }
9189 4
        $haystack = (string) $haystack_tmp;
9190
9191 4
        if ($offset < 0) {
9192
            $offset = 0;
9193
        }
9194
9195 4
        $pos = \strpos($haystack, $needle);
9196 4
        if ($pos === false) {
9197 2
            return false;
9198
        }
9199
9200 4
        if ($pos) {
9201 4
            return $offset + (int) self::strlen(\substr($haystack, 0, $pos), $encoding);
9202
        }
9203
9204 2
        return $offset + 0;
9205
    }
9206
9207
    /**
9208
     * Find the position of the first occurrence of a substring in a string.
9209
     *
9210
     * @param string $haystack <p>
9211
     *                         The string being checked.
9212
     *                         </p>
9213
     * @param string $needle   <p>
9214
     *                         The position counted from the beginning of haystack.
9215
     *                         </p>
9216
     * @param int    $offset   [optional] <p>
9217
     *                         The search offset. If it is not specified, 0 is used.
9218
     *                         </p>
9219
     *
9220
     * @return false|int The numeric position of the first occurrence of needle in the
9221
     *                   haystack string. If needle is not found, it returns false.
9222
     */
9223
    public static function strpos_in_byte(string $haystack, string $needle, int $offset = 0)
9224
    {
9225
        if ($haystack === '' || $needle === '') {
9226
            return false;
9227
        }
9228
9229
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
9230
            // "mb_" is available if overload is used, so use it ...
9231
            return \mb_strpos($haystack, $needle, $offset, 'CP850'); // 8-BIT
9232
        }
9233
9234
        return \strpos($haystack, $needle, $offset);
9235
    }
9236
9237
    /**
9238
     * Find the last occurrence of a character in a string within another.
9239
     *
9240
     * @see http://php.net/manual/en/function.mb-strrchr.php
9241
     *
9242
     * @param string $haystack      <p>The string from which to get the last occurrence of needle.</p>
9243
     * @param string $needle        <p>The string to find in haystack</p>
9244
     * @param bool   $before_needle [optional] <p>
9245
     *                              Determines which portion of haystack
9246
     *                              this function returns.
9247
     *                              If set to true, it returns all of haystack
9248
     *                              from the beginning to the last occurrence of needle.
9249
     *                              If set to false, it returns all of haystack
9250
     *                              from the last occurrence of needle to the end,
9251
     *                              </p>
9252
     * @param string $encoding      [optional] <p>Set the charset for e.g. "mb_" function</p>
9253
     * @param bool   $clean_utf8    [optional] <p>Remove non UTF-8 chars from the string.</p>
9254
     *
9255
     * @return false|string the portion of haystack or false if needle is not found
9256
     */
9257
    public static function strrchr(
9258
        string $haystack,
9259
        string $needle,
9260
        bool $before_needle = false,
9261
        string $encoding = 'UTF-8',
9262
        bool $clean_utf8 = false
9263
    ) {
9264 2
        if ($haystack === '' || $needle === '') {
9265 2
            return false;
9266
        }
9267
9268 2
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9269 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9270
        }
9271
9272 2
        if ($clean_utf8 === true) {
9273
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
9274
            // if invalid characters are found in $haystack before $needle
9275 2
            $needle = self::clean($needle);
9276 2
            $haystack = self::clean($haystack);
9277
        }
9278
9279
        //
9280
        // fallback via mbstring
9281
        //
9282
9283 2
        if (self::$SUPPORT['mbstring'] === true) {
9284 2
            if ($encoding === 'UTF-8') {
9285 2
                return \mb_strrchr($haystack, $needle, $before_needle);
9286
            }
9287
9288 2
            return \mb_strrchr($haystack, $needle, $before_needle, $encoding);
9289
        }
9290
9291
        //
9292
        // fallback for binary || ascii only
9293
        //
9294
9295
        if (
9296
            $before_needle === false
9297
            &&
9298
            (
9299
                $encoding === 'CP850'
9300
                ||
9301
                $encoding === 'ASCII'
9302
            )
9303
        ) {
9304
            return \strrchr($haystack, $needle);
9305
        }
9306
9307
        if (
9308
            $encoding !== 'UTF-8'
9309
            &&
9310
            self::$SUPPORT['mbstring'] === false
9311
        ) {
9312
            \trigger_error('UTF8::strrchr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
9313
        }
9314
9315
        //
9316
        // fallback via iconv
9317
        //
9318
9319
        if (self::$SUPPORT['iconv'] === true) {
9320
            $needle_tmp = self::substr($needle, 0, 1, $encoding);
9321
            if ($needle_tmp === false) {
9322
                return false;
9323
            }
9324
            $needle = (string) $needle_tmp;
9325
9326
            $pos = \iconv_strrpos($haystack, $needle, $encoding);
9327
            if ($pos === false) {
9328
                return false;
9329
            }
9330
9331
            if ($before_needle) {
9332
                return self::substr($haystack, 0, $pos, $encoding);
9333
            }
9334
9335
            return self::substr($haystack, $pos, null, $encoding);
9336
        }
9337
9338
        //
9339
        // fallback via vanilla php
9340
        //
9341
9342
        $needle_tmp = self::substr($needle, 0, 1, $encoding);
9343
        if ($needle_tmp === false) {
9344
            return false;
9345
        }
9346
        $needle = (string) $needle_tmp;
9347
9348
        $pos = self::strrpos($haystack, $needle, 0, $encoding);
9349
        if ($pos === false) {
9350
            return false;
9351
        }
9352
9353
        if ($before_needle) {
9354
            return self::substr($haystack, 0, $pos, $encoding);
9355
        }
9356
9357
        return self::substr($haystack, $pos, null, $encoding);
9358
    }
9359
9360
    /**
9361
     * Reverses characters order in the string.
9362
     *
9363
     * @param string $str      <p>The input string.</p>
9364
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
9365
     *
9366
     * @return string the string with characters in the reverse sequence
9367
     */
9368
    public static function strrev(string $str, string $encoding = 'UTF-8'): string
9369
    {
9370 10
        if ($str === '') {
9371 4
            return '';
9372
        }
9373
9374
        // init
9375 8
        $reversed = '';
9376
9377 8
        $str = self::emoji_encode($str, true);
9378
9379 8
        if ($encoding === 'UTF-8') {
9380 8
            if (self::$SUPPORT['intl'] === true) {
9381
                // try "grapheme" first: https://stackoverflow.com/questions/17496493/strrev-dosent-support-utf-8
9382 8
                $i = (int) \grapheme_strlen($str);
9383 8
                while ($i--) {
9384 8
                    $reversed_tmp = \grapheme_substr($str, $i, 1);
9385 8
                    if ($reversed_tmp !== false) {
9386 8
                        $reversed .= $reversed_tmp;
9387
                    }
9388
                }
9389
            } else {
9390
                $i = (int) \mb_strlen($str);
9391 8
                while ($i--) {
9392
                    $reversed_tmp = \mb_substr($str, $i, 1);
9393
                    if ($reversed_tmp !== false) {
9394
                        $reversed .= $reversed_tmp;
9395
                    }
9396
                }
9397
            }
9398
        } else {
9399
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9400
9401
            $i = (int) self::strlen($str, $encoding);
9402
            while ($i--) {
9403
                $reversed_tmp = self::substr($str, $i, 1, $encoding);
9404
                if ($reversed_tmp !== false) {
9405
                    $reversed .= $reversed_tmp;
9406
                }
9407
            }
9408
        }
9409
9410 8
        return self::emoji_decode($reversed, true);
9411
    }
9412
9413
    /**
9414
     * Find the last occurrence of a character in a string within another, case-insensitive.
9415
     *
9416
     * @see http://php.net/manual/en/function.mb-strrichr.php
9417
     *
9418
     * @param string $haystack      <p>The string from which to get the last occurrence of needle.</p>
9419
     * @param string $needle        <p>The string to find in haystack.</p>
9420
     * @param bool   $before_needle [optional] <p>
9421
     *                              Determines which portion of haystack
9422
     *                              this function returns.
9423
     *                              If set to true, it returns all of haystack
9424
     *                              from the beginning to the last occurrence of needle.
9425
     *                              If set to false, it returns all of haystack
9426
     *                              from the last occurrence of needle to the end,
9427
     *                              </p>
9428
     * @param string $encoding      [optional] <p>Set the charset for e.g. "mb_" function</p>
9429
     * @param bool   $clean_utf8    [optional] <p>Remove non UTF-8 chars from the string.</p>
9430
     *
9431
     * @return false|string the portion of haystack or<br>false if needle is not found
9432
     */
9433
    public static function strrichr(
9434
        string $haystack,
9435
        string $needle,
9436
        bool $before_needle = false,
9437
        string $encoding = 'UTF-8',
9438
        bool $clean_utf8 = false
9439
    ) {
9440 3
        if ($haystack === '' || $needle === '') {
9441 2
            return false;
9442
        }
9443
9444 3
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9445 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9446
        }
9447
9448 3
        if ($clean_utf8 === true) {
9449
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
9450
            // if invalid characters are found in $haystack before $needle
9451 2
            $needle = self::clean($needle);
9452 2
            $haystack = self::clean($haystack);
9453
        }
9454
9455
        //
9456
        // fallback via mbstring
9457
        //
9458
9459 3
        if (self::$SUPPORT['mbstring'] === true) {
9460 3
            if ($encoding === 'UTF-8') {
9461 3
                return \mb_strrichr($haystack, $needle, $before_needle);
9462
            }
9463
9464 2
            return \mb_strrichr($haystack, $needle, $before_needle, $encoding);
9465
        }
9466
9467
        //
9468
        // fallback via vanilla php
9469
        //
9470
9471
        $needle_tmp = self::substr($needle, 0, 1, $encoding);
9472
        if ($needle_tmp === false) {
9473
            return false;
9474
        }
9475
        $needle = (string) $needle_tmp;
9476
9477
        $pos = self::strripos($haystack, $needle, 0, $encoding);
9478
        if ($pos === false) {
9479
            return false;
9480
        }
9481
9482
        if ($before_needle) {
9483
            return self::substr($haystack, 0, $pos, $encoding);
9484
        }
9485
9486
        return self::substr($haystack, $pos, null, $encoding);
9487
    }
9488
9489
    /**
9490
     * Find the position of the last occurrence of a substring in a string, case-insensitive.
9491
     *
9492
     * @param string     $haystack   <p>The string to look in.</p>
9493
     * @param int|string $needle     <p>The string to look for.</p>
9494
     * @param int        $offset     [optional] <p>Number of characters to ignore in the beginning or end.</p>
9495
     * @param string     $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
9496
     * @param bool       $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
9497
     *
9498
     * @return false|int
9499
     *                   <p>The <strong>(int)</strong> numeric position of the last occurrence of needle in the haystack
9500
     *                   string.<br>If needle is not found, it returns false.</p>
9501
     */
9502
    public static function strripos(
9503
        string $haystack,
9504
        $needle,
9505
        int $offset = 0,
9506
        string $encoding = 'UTF-8',
9507
        bool $clean_utf8 = false
9508
    ) {
9509 3
        if ($haystack === '') {
9510
            return false;
9511
        }
9512
9513
        // iconv and mbstring do not support integer $needle
9514 3
        if ((int) $needle === $needle && $needle >= 0) {
9515
            $needle = (string) self::chr($needle);
9516
        }
9517 3
        $needle = (string) $needle;
9518
9519 3
        if ($needle === '') {
9520
            return false;
9521
        }
9522
9523 3
        if ($clean_utf8 === true) {
9524
            // mb_strripos() && iconv_strripos() is not tolerant to invalid characters
9525 2
            $needle = self::clean($needle);
9526 2
            $haystack = self::clean($haystack);
9527
        }
9528
9529 3
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9530 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9531
        }
9532
9533
        //
9534
        // fallback via mbstrig
9535
        //
9536
9537 3
        if (self::$SUPPORT['mbstring'] === true) {
9538 3
            if ($encoding === 'UTF-8') {
9539 3
                return \mb_strripos($haystack, $needle, $offset);
9540
            }
9541
9542
            return \mb_strripos($haystack, $needle, $offset, $encoding);
9543
        }
9544
9545
        //
9546
        // fallback for binary || ascii only
9547
        //
9548
9549
        if (
9550
            $encoding === 'CP850'
9551
            ||
9552
            $encoding === 'ASCII'
9553
        ) {
9554
            return \strripos($haystack, $needle, $offset);
9555
        }
9556
9557
        if (
9558
            $encoding !== 'UTF-8'
9559
            &&
9560
            self::$SUPPORT['mbstring'] === false
9561
        ) {
9562
            \trigger_error('UTF8::strripos() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
9563
        }
9564
9565
        //
9566
        // fallback via intl
9567
        //
9568
9569
        if (
9570
            $encoding === 'UTF-8' // INFO: "grapheme_strripos()" can't handle other encodings
9571
            &&
9572
            $offset >= 0 // grapheme_strripos() can't handle negative offset
9573
            &&
9574
            self::$SUPPORT['intl'] === true
9575
        ) {
9576
            $return_tmp = \grapheme_strripos($haystack, $needle, $offset);
9577
            if ($return_tmp !== false) {
9578
                return $return_tmp;
9579
            }
9580
        }
9581
9582
        //
9583
        // fallback for ascii only
9584
        //
9585
9586
        if (ASCII::is_ascii($haystack . $needle)) {
9587
            return \strripos($haystack, $needle, $offset);
9588
        }
9589
9590
        //
9591
        // fallback via vanilla php
9592
        //
9593
9594
        $haystack = self::strtocasefold($haystack, true, false, $encoding);
9595
        $needle = self::strtocasefold($needle, true, false, $encoding);
9596
9597
        return self::strrpos($haystack, $needle, $offset, $encoding, $clean_utf8);
9598
    }
9599
9600
    /**
9601
     * Finds position of last occurrence of a string within another, case-insensitive.
9602
     *
9603
     * @param string $haystack <p>
9604
     *                         The string from which to get the position of the last occurrence
9605
     *                         of needle.
9606
     *                         </p>
9607
     * @param string $needle   <p>
9608
     *                         The string to find in haystack.
9609
     *                         </p>
9610
     * @param int    $offset   [optional] <p>
9611
     *                         The position in haystack
9612
     *                         to start searching.
9613
     *                         </p>
9614
     *
9615
     * @return false|int
9616
     *                   <p>eturn the numeric position of the last occurrence of needle in the
9617
     *                   haystack string, or false if needle is not found.</p>
9618
     */
9619
    public static function strripos_in_byte(string $haystack, string $needle, int $offset = 0)
9620
    {
9621
        if ($haystack === '' || $needle === '') {
9622
            return false;
9623
        }
9624
9625
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
9626
            // "mb_" is available if overload is used, so use it ...
9627
            return \mb_strripos($haystack, $needle, $offset, 'CP850'); // 8-BIT
9628
        }
9629
9630
        return \strripos($haystack, $needle, $offset);
9631
    }
9632
9633
    /**
9634
     * Find the position of the last occurrence of a substring in a string.
9635
     *
9636
     * @see http://php.net/manual/en/function.mb-strrpos.php
9637
     *
9638
     * @param string     $haystack   <p>The string being checked, for the last occurrence of needle</p>
9639
     * @param int|string $needle     <p>The string to find in haystack.<br>Or a code point as int.</p>
9640
     * @param int        $offset     [optional] <p>May be specified to begin searching an arbitrary number of characters
9641
     *                               into the string. Negative values will stop searching at an arbitrary point prior to
9642
     *                               the end of the string.
9643
     *                               </p>
9644
     * @param string     $encoding   [optional] <p>Set the charset.</p>
9645
     * @param bool       $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
9646
     *
9647
     * @return false|int
9648
     *                   <p>The <strong>(int)</strong> numeric position of the last occurrence of needle in the haystack
9649
     *                   string.<br>If needle is not found, it returns false.</p>
9650
     */
9651
    public static function strrpos(
9652
        string $haystack,
9653
        $needle,
9654
        int $offset = 0,
9655
        string $encoding = 'UTF-8',
9656
        bool $clean_utf8 = false
9657
    ) {
9658 35
        if ($haystack === '') {
9659 3
            return false;
9660
        }
9661
9662
        // iconv and mbstring do not support integer $needle
9663 34
        if ((int) $needle === $needle && $needle >= 0) {
9664 2
            $needle = (string) self::chr($needle);
9665
        }
9666 34
        $needle = (string) $needle;
9667
9668 34
        if ($needle === '') {
9669 2
            return false;
9670
        }
9671
9672 34
        if ($clean_utf8 === true) {
9673
            // mb_strrpos && iconv_strrpos is not tolerant to invalid characters
9674 4
            $needle = self::clean($needle);
9675 4
            $haystack = self::clean($haystack);
9676
        }
9677
9678 34
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9679 8
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9680
        }
9681
9682
        //
9683
        // fallback via mbstring
9684
        //
9685
9686 34
        if (self::$SUPPORT['mbstring'] === true) {
9687 34
            if ($encoding === 'UTF-8') {
9688 34
                return \mb_strrpos($haystack, $needle, $offset);
9689
            }
9690
9691 2
            return \mb_strrpos($haystack, $needle, $offset, $encoding);
9692
        }
9693
9694
        //
9695
        // fallback for binary || ascii only
9696
        //
9697
9698
        if (
9699
            $encoding === 'CP850'
9700
            ||
9701
            $encoding === 'ASCII'
9702
        ) {
9703
            return \strrpos($haystack, $needle, $offset);
9704
        }
9705
9706
        if (
9707
            $encoding !== 'UTF-8'
9708
            &&
9709
            self::$SUPPORT['mbstring'] === false
9710
        ) {
9711
            \trigger_error('UTF8::strrpos() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
9712
        }
9713
9714
        //
9715
        // fallback via intl
9716
        //
9717
9718
        if (
9719
            $offset >= 0 // grapheme_strrpos() can't handle negative offset
9720
            &&
9721
            $encoding === 'UTF-8' // INFO: "grapheme_strrpos()" can't handle other encodings
9722
            &&
9723
            self::$SUPPORT['intl'] === true
9724
        ) {
9725
            $return_tmp = \grapheme_strrpos($haystack, $needle, $offset);
9726
            if ($return_tmp !== false) {
9727
                return $return_tmp;
9728
            }
9729
        }
9730
9731
        //
9732
        // fallback for ascii only
9733
        //
9734
9735
        if (ASCII::is_ascii($haystack . $needle)) {
9736
            return \strrpos($haystack, $needle, $offset);
9737
        }
9738
9739
        //
9740
        // fallback via vanilla php
9741
        //
9742
9743
        $haystack_tmp = null;
9744
        if ($offset > 0) {
9745
            $haystack_tmp = self::substr($haystack, $offset);
9746
        } elseif ($offset < 0) {
9747
            $haystack_tmp = self::substr($haystack, 0, $offset);
9748
            $offset = 0;
9749
        }
9750
9751
        if ($haystack_tmp !== null) {
9752
            if ($haystack_tmp === false) {
9753
                $haystack_tmp = '';
9754
            }
9755
            $haystack = (string) $haystack_tmp;
9756
        }
9757
9758
        $pos = \strrpos($haystack, $needle);
9759
        if ($pos === false) {
9760
            return false;
9761
        }
9762
9763
        /** @var false|string $str_tmp - needed for PhpStan (stubs error) */
9764
        $str_tmp = \substr($haystack, 0, $pos);
9765
        if ($str_tmp === false) {
9766
            return false;
9767
        }
9768
9769
        return $offset + (int) self::strlen($str_tmp);
9770
    }
9771
9772
    /**
9773
     * Find the position of the last occurrence of a substring in a string.
9774
     *
9775
     * @param string $haystack <p>
9776
     *                         The string being checked, for the last occurrence
9777
     *                         of needle.
9778
     *                         </p>
9779
     * @param string $needle   <p>
9780
     *                         The string to find in haystack.
9781
     *                         </p>
9782
     * @param int    $offset   [optional] <p>May be specified to begin searching an arbitrary number of characters into
9783
     *                         the string. Negative values will stop searching at an arbitrary point
9784
     *                         prior to the end of the string.
9785
     *                         </p>
9786
     *
9787
     * @return false|int
9788
     *                   <p>The numeric position of the last occurrence of needle in the
9789
     *                   haystack string. If needle is not found, it returns false.</p>
9790
     */
9791
    public static function strrpos_in_byte(string $haystack, string $needle, int $offset = 0)
9792
    {
9793
        if ($haystack === '' || $needle === '') {
9794
            return false;
9795
        }
9796
9797
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
9798
            // "mb_" is available if overload is used, so use it ...
9799
            return \mb_strrpos($haystack, $needle, $offset, 'CP850'); // 8-BIT
9800
        }
9801
9802
        return \strrpos($haystack, $needle, $offset);
9803
    }
9804
9805
    /**
9806
     * Finds the length of the initial segment of a string consisting entirely of characters contained within a given
9807
     * mask.
9808
     *
9809
     * @param string $str      <p>The input string.</p>
9810
     * @param string $mask     <p>The mask of chars</p>
9811
     * @param int    $offset   [optional]
9812
     * @param int    $length   [optional]
9813
     * @param string $encoding [optional] <p>Set the charset.</p>
9814
     *
9815
     * @return false|int
9816
     */
9817
    public static function strspn(
9818
        string $str,
9819
        string $mask,
9820
        int $offset = 0,
9821
        int $length = null,
9822
        string $encoding = 'UTF-8'
9823
    ) {
9824 10
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9825
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9826
        }
9827
9828 10
        if ($offset || $length !== null) {
9829 2
            if ($encoding === 'UTF-8') {
9830 2
                if ($length === null) {
9831
                    $str = (string) \mb_substr($str, $offset);
9832
                } else {
9833 2
                    $str = (string) \mb_substr($str, $offset, $length);
9834
                }
9835
            } else {
9836
                $str = (string) self::substr($str, $offset, $length, $encoding);
9837
            }
9838
        }
9839
9840 10
        if ($str === '' || $mask === '') {
9841 2
            return 0;
9842
        }
9843
9844 8
        $matches = [];
9845
9846 8
        return \preg_match('/^' . self::rxClass($mask) . '+/u', $str, $matches) ? (int) self::strlen($matches[0], $encoding) : 0;
9847
    }
9848
9849
    /**
9850
     * Returns part of haystack string from the first occurrence of needle to the end of haystack.
9851
     *
9852
     * @param string $haystack      <p>The input string. Must be valid UTF-8.</p>
9853
     * @param string $needle        <p>The string to look for. Must be valid UTF-8.</p>
9854
     * @param bool   $before_needle [optional] <p>
9855
     *                              If <b>TRUE</b>, strstr() returns the part of the
9856
     *                              haystack before the first occurrence of the needle (excluding the needle).
9857
     *                              </p>
9858
     * @param string $encoding      [optional] <p>Set the charset for e.g. "mb_" function</p>
9859
     * @param bool   $clean_utf8    [optional] <p>Remove non UTF-8 chars from the string.</p>
9860
     *
9861
     * @return false|string
9862
     *                      A sub-string,<br>or <strong>false</strong> if needle is not found
9863
     */
9864
    public static function strstr(
9865
        string $haystack,
9866
        string $needle,
9867
        bool $before_needle = false,
9868
        string $encoding = 'UTF-8',
9869
        $clean_utf8 = false
9870
    ) {
9871 3
        if ($haystack === '' || $needle === '') {
9872 2
            return false;
9873
        }
9874
9875 3
        if ($clean_utf8 === true) {
9876
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
9877
            // if invalid characters are found in $haystack before $needle
9878
            $needle = self::clean($needle);
9879
            $haystack = self::clean($haystack);
9880
        }
9881
9882 3
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9883 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9884
        }
9885
9886
        //
9887
        // fallback via mbstring
9888
        //
9889
9890 3
        if (self::$SUPPORT['mbstring'] === true) {
9891 3
            if ($encoding === 'UTF-8') {
9892 3
                return \mb_strstr($haystack, $needle, $before_needle);
9893
            }
9894
9895 2
            return \mb_strstr($haystack, $needle, $before_needle, $encoding);
9896
        }
9897
9898
        //
9899
        // fallback for binary || ascii only
9900
        //
9901
9902
        if (
9903
            $encoding === 'CP850'
9904
            ||
9905
            $encoding === 'ASCII'
9906
        ) {
9907
            return \strstr($haystack, $needle, $before_needle);
9908
        }
9909
9910
        if (
9911
            $encoding !== 'UTF-8'
9912
            &&
9913
            self::$SUPPORT['mbstring'] === false
9914
        ) {
9915
            \trigger_error('UTF8::strstr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
9916
        }
9917
9918
        //
9919
        // fallback via intl
9920
        //
9921
9922
        if (
9923
            $encoding === 'UTF-8' // INFO: "grapheme_strstr()" can't handle other encodings
9924
            &&
9925
            self::$SUPPORT['intl'] === true
9926
        ) {
9927
            $return_tmp = \grapheme_strstr($haystack, $needle, $before_needle);
9928
            if ($return_tmp !== false) {
9929
                return $return_tmp;
9930
            }
9931
        }
9932
9933
        //
9934
        // fallback for ascii only
9935
        //
9936
9937
        if (ASCII::is_ascii($haystack . $needle)) {
9938
            return \strstr($haystack, $needle, $before_needle);
9939
        }
9940
9941
        //
9942
        // fallback via vanilla php
9943
        //
9944
9945
        \preg_match('/^(.*?)' . \preg_quote($needle, '/') . '/us', $haystack, $match);
9946
9947
        if (!isset($match[1])) {
9948
            return false;
9949
        }
9950
9951
        if ($before_needle) {
9952
            return $match[1];
9953
        }
9954
9955
        return self::substr($haystack, (int) self::strlen($match[1]));
9956
    }
9957
9958
    /**
9959
     *  * Finds first occurrence of a string within another.
9960
     *
9961
     * @param string $haystack      <p>
9962
     *                              The string from which to get the first occurrence
9963
     *                              of needle.
9964
     *                              </p>
9965
     * @param string $needle        <p>
9966
     *                              The string to find in haystack.
9967
     *                              </p>
9968
     * @param bool   $before_needle [optional] <p>
9969
     *                              Determines which portion of haystack
9970
     *                              this function returns.
9971
     *                              If set to true, it returns all of haystack
9972
     *                              from the beginning to the first occurrence of needle.
9973
     *                              If set to false, it returns all of haystack
9974
     *                              from the first occurrence of needle to the end,
9975
     *                              </p>
9976
     *
9977
     * @return false|string
9978
     *                      <p>The portion of haystack,
9979
     *                      or false if needle is not found.</p>
9980
     */
9981
    public static function strstr_in_byte(
9982
        string $haystack,
9983
        string $needle,
9984
        bool $before_needle = false
9985
    ) {
9986
        if ($haystack === '' || $needle === '') {
9987
            return false;
9988
        }
9989
9990
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
9991
            // "mb_" is available if overload is used, so use it ...
9992
            return \mb_strstr($haystack, $needle, $before_needle, 'CP850'); // 8-BIT
9993
        }
9994
9995
        return \strstr($haystack, $needle, $before_needle);
9996
    }
9997
9998
    /**
9999
     * Unicode transformation for case-less matching.
10000
     *
10001
     * @see http://unicode.org/reports/tr21/tr21-5.html
10002
     *
10003
     * @param string      $str        <p>The input string.</p>
10004
     * @param bool        $full       [optional] <p>
10005
     *                                <b>true</b>, replace full case folding chars (default)<br>
10006
     *                                <b>false</b>, use only limited static array [UTF8::$COMMON_CASE_FOLD]
10007
     *                                </p>
10008
     * @param bool        $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
10009
     * @param string      $encoding   [optional] <p>Set the charset.</p>
10010
     * @param string|null $lang       [optional] <p>Set the language for special cases: az, el, lt, tr</p>
10011
     * @param bool        $lower      [optional] <p>Use lowercase string, otherwise use uppercase string. PS: uppercase
10012
     *                                is for some languages better ...</p>
10013
     *
10014
     * @return string
10015
     */
10016
    public static function strtocasefold(
10017
        string $str,
10018
        bool $full = true,
10019
        bool $clean_utf8 = false,
10020
        string $encoding = 'UTF-8',
10021
        string $lang = null,
10022
        $lower = true
10023
    ): string {
10024 32
        if ($str === '') {
10025 5
            return '';
10026
        }
10027
10028 31
        if ($clean_utf8 === true) {
10029
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
10030
            // if invalid characters are found in $haystack before $needle
10031 2
            $str = self::clean($str);
10032
        }
10033
10034 31
        $str = self::fixStrCaseHelper($str, $lower, $full);
10035
10036 31
        if ($lang === null && $encoding === 'UTF-8') {
10037 31
            if ($lower === true) {
10038 2
                return \mb_strtolower($str);
10039
            }
10040
10041 29
            return \mb_strtoupper($str);
10042
        }
10043
10044 2
        if ($lower === true) {
10045
            return self::strtolower($str, $encoding, false, $lang);
10046
        }
10047
10048 2
        return self::strtoupper($str, $encoding, false, $lang);
10049
    }
10050
10051
    /**
10052
     * Make a string lowercase.
10053
     *
10054
     * @see http://php.net/manual/en/function.mb-strtolower.php
10055
     *
10056
     * @param string      $str                           <p>The string being lowercased.</p>
10057
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
10058
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
10059
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
10060
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
10061
     *
10062
     * @return string
10063
     *                <p>String with all alphabetic characters converted to lowercase.</p>
10064
     */
10065
    public static function strtolower(
10066
        $str,
10067
        string $encoding = 'UTF-8',
10068
        bool $clean_utf8 = false,
10069
        string $lang = null,
10070
        bool $try_to_keep_the_string_length = false
10071
    ): string {
10072
        // init
10073 73
        $str = (string) $str;
10074
10075 73
        if ($str === '') {
10076 1
            return '';
10077
        }
10078
10079 72
        if ($clean_utf8 === true) {
10080
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
10081
            // if invalid characters are found in $haystack before $needle
10082 2
            $str = self::clean($str);
10083
        }
10084
10085
        // hack for old php version or for the polyfill ...
10086 72
        if ($try_to_keep_the_string_length === true) {
10087
            $str = self::fixStrCaseHelper($str, true);
10088
        }
10089
10090 72
        if ($lang === null && $encoding === 'UTF-8') {
10091 13
            return \mb_strtolower($str);
10092
        }
10093
10094 61
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
10095
10096 61
        if ($lang !== null) {
10097 2
            if (self::$SUPPORT['intl'] === true) {
10098 2
                if (self::$INTL_TRANSLITERATOR_LIST === null) {
10099
                    self::$INTL_TRANSLITERATOR_LIST = self::getData('transliterator_list');
10100
                }
10101
10102 2
                $language_code = $lang . '-Lower';
10103 2
                if (!\in_array($language_code, self::$INTL_TRANSLITERATOR_LIST, true)) {
10104
                    \trigger_error('UTF8::strtolower() cannot handle special language: ' . $lang . ' | supported: ' . \print_r(self::$INTL_TRANSLITERATOR_LIST, true), \E_USER_WARNING);
10105
10106
                    $language_code = 'Any-Lower';
10107
                }
10108
10109
                /** @noinspection PhpComposerExtensionStubsInspection */
10110
                /** @noinspection UnnecessaryCastingInspection */
10111 2
                return (string) \transliterator_transliterate($language_code, $str);
10112
            }
10113
10114
            \trigger_error('UTF8::strtolower() without intl cannot handle the "lang" parameter: ' . $lang, \E_USER_WARNING);
10115
        }
10116
10117
        // always fallback via symfony polyfill
10118 61
        return \mb_strtolower($str, $encoding);
10119
    }
10120
10121
    /**
10122
     * Make a string uppercase.
10123
     *
10124
     * @see http://php.net/manual/en/function.mb-strtoupper.php
10125
     *
10126
     * @param string      $str                           <p>The string being uppercased.</p>
10127
     * @param string      $encoding                      [optional] <p>Set the charset.</p>
10128
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
10129
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
10130
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
10131
     *
10132
     * @return string
10133
     *                <p>String with all alphabetic characters converted to uppercase.</p>
10134
     */
10135
    public static function strtoupper(
10136
        $str,
10137
        string $encoding = 'UTF-8',
10138
        bool $clean_utf8 = false,
10139
        string $lang = null,
10140
        bool $try_to_keep_the_string_length = false
10141
    ): string {
10142
        // init
10143 17
        $str = (string) $str;
10144
10145 17
        if ($str === '') {
10146 1
            return '';
10147
        }
10148
10149 16
        if ($clean_utf8 === true) {
10150
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
10151
            // if invalid characters are found in $haystack before $needle
10152 2
            $str = self::clean($str);
10153
        }
10154
10155
        // hack for old php version or for the polyfill ...
10156 16
        if ($try_to_keep_the_string_length === true) {
10157 2
            $str = self::fixStrCaseHelper($str, false);
10158
        }
10159
10160 16
        if ($lang === null && $encoding === 'UTF-8') {
10161 8
            return \mb_strtoupper($str);
10162
        }
10163
10164 10
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
10165
10166 10
        if ($lang !== null) {
10167 2
            if (self::$SUPPORT['intl'] === true) {
10168 2
                if (self::$INTL_TRANSLITERATOR_LIST === null) {
10169
                    self::$INTL_TRANSLITERATOR_LIST = self::getData('transliterator_list');
10170
                }
10171
10172 2
                $language_code = $lang . '-Upper';
10173 2
                if (!\in_array($language_code, self::$INTL_TRANSLITERATOR_LIST, true)) {
10174
                    \trigger_error('UTF8::strtoupper() without intl for special language: ' . $lang, \E_USER_WARNING);
10175
10176
                    $language_code = 'Any-Upper';
10177
                }
10178
10179
                /** @noinspection PhpComposerExtensionStubsInspection */
10180
                /** @noinspection UnnecessaryCastingInspection */
10181 2
                return (string) \transliterator_transliterate($language_code, $str);
10182
            }
10183
10184
            \trigger_error('UTF8::strtolower() without intl cannot handle the "lang"-parameter: ' . $lang, \E_USER_WARNING);
10185
        }
10186
10187
        // always fallback via symfony polyfill
10188 10
        return \mb_strtoupper($str, $encoding);
10189
    }
10190
10191
    /**
10192
     * Translate characters or replace sub-strings.
10193
     *
10194
     * @see http://php.net/manual/en/function.strtr.php
10195
     *
10196
     * @param string          $str  <p>The string being translated.</p>
10197
     * @param string|string[] $from <p>The string replacing from.</p>
10198
     * @param string|string[] $to   [optional] <p>The string being translated to to.</p>
10199
     *
10200
     * @return string
10201
     *                <p>This function returns a copy of str, translating all occurrences of each character in "from" to the
10202
     *                corresponding character in "to".</p>
10203
     */
10204
    public static function strtr(string $str, $from, $to = ''): string
10205
    {
10206 2
        if ($str === '') {
10207
            return '';
10208
        }
10209
10210 2
        if ($from === $to) {
10211
            return $str;
10212
        }
10213
10214 2
        if ($to !== '') {
10215 2
            $from = self::str_split($from);
10216 2
            $to = self::str_split($to);
10217 2
            $count_from = \count($from);
10218 2
            $count_to = \count($to);
10219
10220 2
            if ($count_from > $count_to) {
10221 2
                $from = \array_slice($from, 0, $count_to);
10222 2
            } elseif ($count_from < $count_to) {
10223 2
                $to = \array_slice($to, 0, $count_from);
10224
            }
10225
10226 2
            $from = \array_combine($from, $to);
10227
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
10228 2
            if ($from === false) {
10229
                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) . ')');
10230
            }
10231
        }
10232
10233 2
        if (\is_string($from)) {
10234 2
            return \str_replace($from, '', $str);
10235
        }
10236
10237 2
        return \strtr($str, $from);
10238
    }
10239
10240
    /**
10241
     * Return the width of a string.
10242
     *
10243
     * @param string $str        <p>The input string.</p>
10244
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
10245
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
10246
     *
10247
     * @return int
10248
     */
10249
    public static function strwidth(
10250
        string $str,
10251
        string $encoding = 'UTF-8',
10252
        bool $clean_utf8 = false
10253
    ): int {
10254 2
        if ($str === '') {
10255 2
            return 0;
10256
        }
10257
10258 2
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
10259 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
10260
        }
10261
10262 2
        if ($clean_utf8 === true) {
10263
            // iconv and mbstring are not tolerant to invalid encoding
10264
            // further, their behaviour is inconsistent with that of PHP's substr
10265 2
            $str = self::clean($str);
10266
        }
10267
10268
        //
10269
        // fallback via mbstring
10270
        //
10271
10272 2
        if (self::$SUPPORT['mbstring'] === true) {
10273 2
            if ($encoding === 'UTF-8') {
10274 2
                return \mb_strwidth($str);
10275
            }
10276
10277
            return \mb_strwidth($str, $encoding);
10278
        }
10279
10280
        //
10281
        // fallback via vanilla php
10282
        //
10283
10284
        if ($encoding !== 'UTF-8') {
10285
            $str = self::encode('UTF-8', $str, false, $encoding);
10286
        }
10287
10288
        $wide = 0;
10289
        $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);
10290
10291
        return ($wide << 1) + (int) self::strlen($str, 'UTF-8');
10292
    }
10293
10294
    /**
10295
     * Get part of a string.
10296
     *
10297
     * @see http://php.net/manual/en/function.mb-substr.php
10298
     *
10299
     * @param string $str        <p>The string being checked.</p>
10300
     * @param int    $offset     <p>The first position used in str.</p>
10301
     * @param int    $length     [optional] <p>The maximum length of the returned string.</p>
10302
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
10303
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
10304
     *
10305
     * @return false|string
10306
     *                      The portion of <i>str</i> specified by the <i>offset</i> and
10307
     *                      <i>length</i> parameters.</p><p>If <i>str</i> is shorter than <i>offset</i>
10308
     *                      characters long, <b>FALSE</b> will be returned.
10309
     */
10310
    public static function substr(
10311
        string $str,
10312
        int $offset = 0,
10313
        int $length = null,
10314
        string $encoding = 'UTF-8',
10315
        bool $clean_utf8 = false
10316
    ) {
10317
        // empty string
10318 172
        if ($str === '' || $length === 0) {
10319 8
            return '';
10320
        }
10321
10322 168
        if ($clean_utf8 === true) {
10323
            // iconv and mbstring are not tolerant to invalid encoding
10324
            // further, their behaviour is inconsistent with that of PHP's substr
10325 2
            $str = self::clean($str);
10326
        }
10327
10328
        // whole string
10329 168
        if (!$offset && $length === null) {
10330 7
            return $str;
10331
        }
10332
10333 163
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
10334 19
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
10335
        }
10336
10337
        //
10338
        // fallback via mbstring
10339
        //
10340
10341 163
        if (self::$SUPPORT['mbstring'] === true) {
10342 161
            if ($encoding === 'UTF-8') {
10343 161
                if ($length === null) {
10344 64
                    return \mb_substr($str, $offset);
10345
                }
10346
10347 102
                return \mb_substr($str, $offset, $length);
10348
            }
10349
10350
            return self::substr($str, $offset, $length, $encoding);
10351
        }
10352
10353
        //
10354
        // fallback for binary || ascii only
10355
        //
10356
10357
        if (
10358 4
            $encoding === 'CP850'
10359
            ||
10360 4
            $encoding === 'ASCII'
10361
        ) {
10362
            if ($length === null) {
10363
                return \substr($str, $offset);
10364
            }
10365
10366
            return \substr($str, $offset, $length);
10367
        }
10368
10369
        // otherwise we need the string-length
10370 4
        $str_length = 0;
10371 4
        if ($offset || $length === null) {
10372 4
            $str_length = self::strlen($str, $encoding);
10373
        }
10374
10375
        // e.g.: invalid chars + mbstring not installed
10376 4
        if ($str_length === false) {
10377
            return false;
10378
        }
10379
10380
        // empty string
10381 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...
10382
            return '';
10383
        }
10384
10385
        // impossible
10386 4
        if ($offset && $offset > $str_length) {
10387
            return '';
10388
        }
10389
10390 4
        if ($length === null) {
10391 4
            $length = (int) $str_length;
10392
        } else {
10393 2
            $length = (int) $length;
10394
        }
10395
10396
        if (
10397 4
            $encoding !== 'UTF-8'
10398
            &&
10399 4
            self::$SUPPORT['mbstring'] === false
10400
        ) {
10401 2
            \trigger_error('UTF8::substr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
10402
        }
10403
10404
        //
10405
        // fallback via intl
10406
        //
10407
10408
        if (
10409 4
            $encoding === 'UTF-8' // INFO: "grapheme_substr()" can't handle other encodings
10410
            &&
10411 4
            $offset >= 0 // grapheme_substr() can't handle negative offset
10412
            &&
10413 4
            self::$SUPPORT['intl'] === true
10414
        ) {
10415
            $return_tmp = \grapheme_substr($str, $offset, $length);
10416
            if ($return_tmp !== false) {
10417
                return $return_tmp;
10418
            }
10419
        }
10420
10421
        //
10422
        // fallback via iconv
10423
        //
10424
10425
        if (
10426 4
            $length >= 0 // "iconv_substr()" can't handle negative length
10427
            &&
10428 4
            self::$SUPPORT['iconv'] === true
10429
        ) {
10430
            $return_tmp = \iconv_substr($str, $offset, $length);
10431
            if ($return_tmp !== false) {
10432
                return $return_tmp;
10433
            }
10434
        }
10435
10436
        //
10437
        // fallback for ascii only
10438
        //
10439
10440 4
        if (ASCII::is_ascii($str)) {
10441
            return \substr($str, $offset, $length);
10442
        }
10443
10444
        //
10445
        // fallback via vanilla php
10446
        //
10447
10448
        // split to array, and remove invalid characters
10449 4
        $array = self::str_split($str);
10450
10451
        // extract relevant part, and join to make sting again
10452 4
        return \implode('', \array_slice($array, $offset, $length));
10453
    }
10454
10455
    /**
10456
     * Binary-safe comparison of two strings from an offset, up to a length of characters.
10457
     *
10458
     * @param string   $str1               <p>The main string being compared.</p>
10459
     * @param string   $str2               <p>The secondary string being compared.</p>
10460
     * @param int      $offset             [optional] <p>The start position for the comparison. If negative, it starts
10461
     *                                     counting from the end of the string.</p>
10462
     * @param int|null $length             [optional] <p>The length of the comparison. The default value is the largest
10463
     *                                     of the length of the str compared to the length of main_str less the
10464
     *                                     offset.</p>
10465
     * @param bool     $case_insensitivity [optional] <p>If case_insensitivity is TRUE, comparison is case
10466
     *                                     insensitive.</p>
10467
     * @param string   $encoding           [optional] <p>Set the charset for e.g. "mb_" function</p>
10468
     *
10469
     * @return int
10470
     *             <strong>&lt; 0</strong> if str1 is less than str2;<br>
10471
     *             <strong>&gt; 0</strong> if str1 is greater than str2,<br>
10472
     *             <strong>0</strong> if they are equal
10473
     */
10474
    public static function substr_compare(
10475
        string $str1,
10476
        string $str2,
10477
        int $offset = 0,
10478
        int $length = null,
10479
        bool $case_insensitivity = false,
10480
        string $encoding = 'UTF-8'
10481
    ): int {
10482
        if (
10483 2
            $offset !== 0
10484
            ||
10485 2
            $length !== null
10486
        ) {
10487 2
            if ($encoding === 'UTF-8') {
10488 2
                if ($length === null) {
10489 2
                    $str1 = (string) \mb_substr($str1, $offset);
10490
                } else {
10491 2
                    $str1 = (string) \mb_substr($str1, $offset, $length);
10492
                }
10493 2
                $str2 = (string) \mb_substr($str2, 0, (int) self::strlen($str1));
10494
            } else {
10495
                $encoding = self::normalize_encoding($encoding, 'UTF-8');
10496
10497
                $str1 = (string) self::substr($str1, $offset, $length, $encoding);
10498
                $str2 = (string) self::substr($str2, 0, (int) self::strlen($str1), $encoding);
10499
            }
10500
        }
10501
10502 2
        if ($case_insensitivity === true) {
10503 2
            return self::strcasecmp($str1, $str2, $encoding);
10504
        }
10505
10506 2
        return self::strcmp($str1, $str2);
10507
    }
10508
10509
    /**
10510
     * Count the number of substring occurrences.
10511
     *
10512
     * @see http://php.net/manual/en/function.substr-count.php
10513
     *
10514
     * @param string $haystack   <p>The string to search in.</p>
10515
     * @param string $needle     <p>The substring to search for.</p>
10516
     * @param int    $offset     [optional] <p>The offset where to start counting.</p>
10517
     * @param int    $length     [optional] <p>
10518
     *                           The maximum length after the specified offset to search for the
10519
     *                           substring. It outputs a warning if the offset plus the length is
10520
     *                           greater than the haystack length.
10521
     *                           </p>
10522
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
10523
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
10524
     *
10525
     * @return false|int this functions returns an integer or false if there isn't a string
10526
     */
10527
    public static function substr_count(
10528
        string $haystack,
10529
        string $needle,
10530
        int $offset = 0,
10531
        int $length = null,
10532
        string $encoding = 'UTF-8',
10533
        bool $clean_utf8 = false
10534
    ) {
10535 5
        if ($haystack === '' || $needle === '') {
10536 2
            return false;
10537
        }
10538
10539 5
        if ($length === 0) {
10540 2
            return 0;
10541
        }
10542
10543 5
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
10544 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
10545
        }
10546
10547 5
        if ($clean_utf8 === true) {
10548
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
10549
            // if invalid characters are found in $haystack before $needle
10550
            $needle = self::clean($needle);
10551
            $haystack = self::clean($haystack);
10552
        }
10553
10554 5
        if ($offset || $length > 0) {
10555 2
            if ($length === null) {
10556 2
                $length_tmp = self::strlen($haystack, $encoding);
10557 2
                if ($length_tmp === false) {
10558
                    return false;
10559
                }
10560 2
                $length = (int) $length_tmp;
10561
            }
10562
10563 2
            if ($encoding === 'UTF-8') {
10564 2
                $haystack = (string) \mb_substr($haystack, $offset, $length);
10565
            } else {
10566 2
                $haystack = (string) \mb_substr($haystack, $offset, $length, $encoding);
10567
            }
10568
        }
10569
10570
        if (
10571 5
            $encoding !== 'UTF-8'
10572
            &&
10573 5
            self::$SUPPORT['mbstring'] === false
10574
        ) {
10575
            \trigger_error('UTF8::substr_count() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
10576
        }
10577
10578 5
        if (self::$SUPPORT['mbstring'] === true) {
10579 5
            if ($encoding === 'UTF-8') {
10580 5
                return \mb_substr_count($haystack, $needle);
10581
            }
10582
10583 2
            return \mb_substr_count($haystack, $needle, $encoding);
10584
        }
10585
10586
        \preg_match_all('/' . \preg_quote($needle, '/') . '/us', $haystack, $matches, \PREG_SET_ORDER);
10587
10588
        return \count($matches);
10589
    }
10590
10591
    /**
10592
     * Count the number of substring occurrences.
10593
     *
10594
     * @param string $haystack <p>
10595
     *                         The string being checked.
10596
     *                         </p>
10597
     * @param string $needle   <p>
10598
     *                         The string being found.
10599
     *                         </p>
10600
     * @param int    $offset   [optional] <p>
10601
     *                         The offset where to start counting
10602
     *                         </p>
10603
     * @param int    $length   [optional] <p>
10604
     *                         The maximum length after the specified offset to search for the
10605
     *                         substring. It outputs a warning if the offset plus the length is
10606
     *                         greater than the haystack length.
10607
     *                         </p>
10608
     *
10609
     * @return false|int the number of times the
10610
     *                   needle substring occurs in the
10611
     *                   haystack string
10612
     */
10613
    public static function substr_count_in_byte(
10614
        string $haystack,
10615
        string $needle,
10616
        int $offset = 0,
10617
        int $length = null
10618
    ) {
10619
        if ($haystack === '' || $needle === '') {
10620
            return 0;
10621
        }
10622
10623
        if (
10624
            ($offset || $length !== null)
10625
            &&
10626
            self::$SUPPORT['mbstring_func_overload'] === true
10627
        ) {
10628
            if ($length === null) {
10629
                $length_tmp = self::strlen($haystack);
10630
                if ($length_tmp === false) {
10631
                    return false;
10632
                }
10633
                $length = (int) $length_tmp;
10634
            }
10635
10636
            if (
10637
                (
10638
                    $length !== 0
10639
                    &&
10640
                    $offset !== 0
10641
                )
10642
                &&
10643
                ($length + $offset) <= 0
10644
                &&
10645
                Bootup::is_php('7.1') === false // output from "substr_count()" have changed in PHP 7.1
10646
            ) {
10647
                return false;
10648
            }
10649
10650
            /** @var false|string $haystack_tmp - needed for PhpStan (stubs error) */
10651
            $haystack_tmp = \substr($haystack, $offset, $length);
10652
            if ($haystack_tmp === false) {
10653
                $haystack_tmp = '';
10654
            }
10655
            $haystack = (string) $haystack_tmp;
10656
        }
10657
10658
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
10659
            // "mb_" is available if overload is used, so use it ...
10660
            return \mb_substr_count($haystack, $needle, 'CP850'); // 8-BIT
10661
        }
10662
10663
        if ($length === null) {
10664
            return \substr_count($haystack, $needle, $offset);
10665
        }
10666
10667
        return \substr_count($haystack, $needle, $offset, $length);
10668
    }
10669
10670
    /**
10671
     * Returns the number of occurrences of $substring in the given string.
10672
     * By default, the comparison is case-sensitive, but can be made insensitive
10673
     * by setting $case_sensitive to false.
10674
     *
10675
     * @param string $str            <p>The input string.</p>
10676
     * @param string $substring      <p>The substring to search for.</p>
10677
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
10678
     * @param string $encoding       [optional] <p>Set the charset for e.g. "mb_" function</p>
10679
     *
10680
     * @return int
10681
     */
10682
    public static function substr_count_simple(
10683
        string $str,
10684
        string $substring,
10685
        bool $case_sensitive = true,
10686
        string $encoding = 'UTF-8'
10687
    ): int {
10688 15
        if ($str === '' || $substring === '') {
10689 2
            return 0;
10690
        }
10691
10692 13
        if ($encoding === 'UTF-8') {
10693 7
            if ($case_sensitive) {
10694
                return (int) \mb_substr_count($str, $substring);
10695
            }
10696
10697 7
            return (int) \mb_substr_count(
10698 7
                \mb_strtoupper($str),
10699 7
                \mb_strtoupper($substring)
10700
            );
10701
        }
10702
10703 6
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
10704
10705 6
        if ($case_sensitive) {
10706 3
            return (int) \mb_substr_count($str, $substring, $encoding);
10707
        }
10708
10709 3
        return (int) \mb_substr_count(
10710 3
            self::strtocasefold($str, true, false, $encoding, null, false),
10711 3
            self::strtocasefold($substring, true, false, $encoding, null, false),
10712 3
            $encoding
10713
        );
10714
    }
10715
10716
    /**
10717
     * Removes a prefix ($needle) from the beginning of the string ($haystack), case-insensitive.
10718
     *
10719
     * @param string $haystack <p>The string to search in.</p>
10720
     * @param string $needle   <p>The substring to search for.</p>
10721
     *
10722
     * @return string return the sub-string
10723
     */
10724
    public static function substr_ileft(string $haystack, string $needle): string
10725
    {
10726 2
        if ($haystack === '') {
10727 2
            return '';
10728
        }
10729
10730 2
        if ($needle === '') {
10731 2
            return $haystack;
10732
        }
10733
10734 2
        if (self::str_istarts_with($haystack, $needle) === true) {
10735 2
            $haystack = (string) \mb_substr($haystack, (int) self::strlen($needle));
10736
        }
10737
10738 2
        return $haystack;
10739
    }
10740
10741
    /**
10742
     * Get part of a string process in bytes.
10743
     *
10744
     * @param string $str    <p>The string being checked.</p>
10745
     * @param int    $offset <p>The first position used in str.</p>
10746
     * @param int    $length [optional] <p>The maximum length of the returned string.</p>
10747
     *
10748
     * @return false|string
10749
     *                      The portion of <i>str</i> specified by the <i>offset</i> and
10750
     *                      <i>length</i> parameters.</p><p>If <i>str</i> is shorter than <i>offset</i>
10751
     *                      characters long, <b>FALSE</b> will be returned.
10752
     */
10753
    public static function substr_in_byte(string $str, int $offset = 0, int $length = null)
10754
    {
10755
        // empty string
10756
        if ($str === '' || $length === 0) {
10757
            return '';
10758
        }
10759
10760
        // whole string
10761
        if (!$offset && $length === null) {
10762
            return $str;
10763
        }
10764
10765
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
10766
            // "mb_" is available if overload is used, so use it ...
10767
            return \mb_substr($str, $offset, $length, 'CP850'); // 8-BIT
10768
        }
10769
10770
        return \substr($str, $offset, $length ?? 2147483647);
10771
    }
10772
10773
    /**
10774
     * Removes a suffix ($needle) from the end of the string ($haystack), case-insensitive.
10775
     *
10776
     * @param string $haystack <p>The string to search in.</p>
10777
     * @param string $needle   <p>The substring to search for.</p>
10778
     *
10779
     * @return string return the sub-string
10780
     */
10781
    public static function substr_iright(string $haystack, string $needle): string
10782
    {
10783 2
        if ($haystack === '') {
10784 2
            return '';
10785
        }
10786
10787 2
        if ($needle === '') {
10788 2
            return $haystack;
10789
        }
10790
10791 2
        if (self::str_iends_with($haystack, $needle) === true) {
10792 2
            $haystack = (string) \mb_substr($haystack, 0, (int) self::strlen($haystack) - (int) self::strlen($needle));
10793
        }
10794
10795 2
        return $haystack;
10796
    }
10797
10798
    /**
10799
     * Removes a prefix ($needle) from the beginning of the string ($haystack).
10800
     *
10801
     * @param string $haystack <p>The string to search in.</p>
10802
     * @param string $needle   <p>The substring to search for.</p>
10803
     *
10804
     * @return string return the sub-string
10805
     */
10806
    public static function substr_left(string $haystack, string $needle): string
10807
    {
10808 2
        if ($haystack === '') {
10809 2
            return '';
10810
        }
10811
10812 2
        if ($needle === '') {
10813 2
            return $haystack;
10814
        }
10815
10816 2
        if (self::str_starts_with($haystack, $needle) === true) {
10817 2
            $haystack = (string) \mb_substr($haystack, (int) self::strlen($needle));
10818
        }
10819
10820 2
        return $haystack;
10821
    }
10822
10823
    /**
10824
     * Replace text within a portion of a string.
10825
     *
10826
     * source: https://gist.github.com/stemar/8287074
10827
     *
10828
     * @param string|string[] $str         <p>The input string or an array of stings.</p>
10829
     * @param string|string[] $replacement <p>The replacement string or an array of stings.</p>
10830
     * @param int|int[]       $offset      <p>
10831
     *                                     If start is positive, the replacing will begin at the start'th offset
10832
     *                                     into string.
10833
     *                                     <br><br>
10834
     *                                     If start is negative, the replacing will begin at the start'th character
10835
     *                                     from the end of string.
10836
     *                                     </p>
10837
     * @param int|int[]|null  $length      [optional] <p>If given and is positive, it represents the length of the
10838
     *                                     portion of string which is to be replaced. If it is negative, it
10839
     *                                     represents the number of characters from the end of string at which to
10840
     *                                     stop replacing. If it is not given, then it will default to strlen(
10841
     *                                     string ); i.e. end the replacing at the end of string. Of course, if
10842
     *                                     length is zero then this function will have the effect of inserting
10843
     *                                     replacement into string at the given start offset.</p>
10844
     * @param string          $encoding    [optional] <p>Set the charset for e.g. "mb_" function</p>
10845
     *
10846
     * @return string|string[] The result string is returned. If string is an array then array is returned.
10847
     */
10848
    public static function substr_replace(
10849
        $str,
10850
        $replacement,
10851
        $offset,
10852
        $length = null,
10853
        string $encoding = 'UTF-8'
10854
    ) {
10855 10
        if (\is_array($str) === true) {
10856 1
            $num = \count($str);
10857
10858
            // the replacement
10859 1
            if (\is_array($replacement) === true) {
10860 1
                $replacement = \array_slice($replacement, 0, $num);
10861
            } else {
10862 1
                $replacement = \array_pad([$replacement], $num, $replacement);
10863
            }
10864
10865
            // the offset
10866 1
            if (\is_array($offset) === true) {
10867 1
                $offset = \array_slice($offset, 0, $num);
10868 1
                foreach ($offset as &$value_tmp) {
10869 1
                    $value_tmp = (int) $value_tmp === $value_tmp ? $value_tmp : 0;
10870
                }
10871 1
                unset($value_tmp);
10872
            } else {
10873 1
                $offset = \array_pad([$offset], $num, $offset);
10874
            }
10875
10876
            // the length
10877 1
            if ($length === null) {
10878 1
                $length = \array_fill(0, $num, 0);
10879 1
            } elseif (\is_array($length) === true) {
10880 1
                $length = \array_slice($length, 0, $num);
10881 1
                foreach ($length as &$value_tmp_V2) {
10882 1
                    $value_tmp_V2 = (int) $value_tmp_V2 === $value_tmp_V2 ? $value_tmp_V2 : $num;
10883
                }
10884 1
                unset($value_tmp_V2);
10885
            } else {
10886 1
                $length = \array_pad([$length], $num, $length);
10887
            }
10888
10889
            // recursive call
10890 1
            return \array_map([self::class, 'substr_replace'], $str, $replacement, $offset, $length);
10891
        }
10892
10893 10
        if (\is_array($replacement) === true) {
10894 1
            if ($replacement !== []) {
10895 1
                $replacement = $replacement[0];
10896
            } else {
10897 1
                $replacement = '';
10898
            }
10899
        }
10900
10901
        // init
10902 10
        $str = (string) $str;
10903 10
        $replacement = (string) $replacement;
10904
10905 10
        if (\is_array($length) === true) {
10906
            throw new \InvalidArgumentException('Parameter "$length" can only be an array, if "$str" is also an array.');
10907
        }
10908
10909 10
        if (\is_array($offset) === true) {
10910
            throw new \InvalidArgumentException('Parameter "$offset" can only be an array, if "$str" is also an array.');
10911
        }
10912
10913 10
        if ($str === '') {
10914 1
            return $replacement;
10915
        }
10916
10917 9
        if (self::$SUPPORT['mbstring'] === true) {
10918 9
            $string_length = (int) self::strlen($str, $encoding);
10919
10920 9
            if ($offset < 0) {
10921 1
                $offset = (int) \max(0, $string_length + $offset);
10922 9
            } elseif ($offset > $string_length) {
10923 1
                $offset = $string_length;
10924
            }
10925
10926 9
            if ($length !== null && $length < 0) {
10927 1
                $length = (int) \max(0, $string_length - $offset + $length);
10928 9
            } elseif ($length === null || $length > $string_length) {
10929 4
                $length = $string_length;
10930
            }
10931
10932
            /** @noinspection AdditionOperationOnArraysInspection */
10933 9
            if (($offset + $length) > $string_length) {
10934 4
                $length = $string_length - $offset;
10935
            }
10936
10937
            /** @noinspection AdditionOperationOnArraysInspection */
10938 9
            return ((string) \mb_substr($str, 0, $offset, $encoding)) .
10939 9
                   $replacement .
10940 9
                   ((string) \mb_substr($str, $offset + $length, $string_length - $offset - $length, $encoding));
10941
        }
10942
10943
        //
10944
        // fallback for ascii only
10945
        //
10946
10947
        if (ASCII::is_ascii($str)) {
10948
            return ($length === null) ?
10949
                \substr_replace($str, $replacement, $offset) :
10950
                \substr_replace($str, $replacement, $offset, $length);
10951
        }
10952
10953
        //
10954
        // fallback via vanilla php
10955
        //
10956
10957
        \preg_match_all('/./us', $str, $str_matches);
10958
        \preg_match_all('/./us', $replacement, $replacement_matches);
10959
10960
        if ($length === null) {
10961
            $length_tmp = self::strlen($str, $encoding);
10962
            if ($length_tmp === false) {
10963
                // e.g.: non mbstring support + invalid chars
10964
                return '';
10965
            }
10966
            $length = (int) $length_tmp;
10967
        }
10968
10969
        \array_splice($str_matches[0], $offset, $length, $replacement_matches[0]);
10970
10971
        return \implode('', $str_matches[0]);
10972
    }
10973
10974
    /**
10975
     * Removes a suffix ($needle) from the end of the string ($haystack).
10976
     *
10977
     * @param string $haystack <p>The string to search in.</p>
10978
     * @param string $needle   <p>The substring to search for.</p>
10979
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
10980
     *
10981
     * @return string return the sub-string
10982
     */
10983
    public static function substr_right(
10984
        string $haystack,
10985
        string $needle,
10986
        string $encoding = 'UTF-8'
10987
    ): string {
10988 2
        if ($haystack === '') {
10989 2
            return '';
10990
        }
10991
10992 2
        if ($needle === '') {
10993 2
            return $haystack;
10994
        }
10995
10996
        if (
10997 2
            $encoding === 'UTF-8'
10998
            &&
10999 2
            \substr($haystack, -\strlen($needle)) === $needle
11000
        ) {
11001 2
            return (string) \mb_substr($haystack, 0, (int) \mb_strlen($haystack) - (int) \mb_strlen($needle));
11002
        }
11003
11004 2
        if (\substr($haystack, -\strlen($needle)) === $needle) {
11005
            return (string) self::substr(
11006
                $haystack,
11007
                0,
11008
                (int) self::strlen($haystack, $encoding) - (int) self::strlen($needle, $encoding),
11009
                $encoding
11010
            );
11011
        }
11012
11013 2
        return $haystack;
11014
    }
11015
11016
    /**
11017
     * Returns a case swapped version of the string.
11018
     *
11019
     * @param string $str        <p>The input string.</p>
11020
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
11021
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
11022
     *
11023
     * @return string each character's case swapped
11024
     */
11025
    public static function swapCase(string $str, string $encoding = 'UTF-8', bool $clean_utf8 = false): string
11026
    {
11027 6
        if ($str === '') {
11028 1
            return '';
11029
        }
11030
11031 6
        if ($clean_utf8 === true) {
11032
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
11033
            // if invalid characters are found in $haystack before $needle
11034 2
            $str = self::clean($str);
11035
        }
11036
11037 6
        if ($encoding === 'UTF-8') {
11038 4
            return (string) (\mb_strtolower($str) ^ \mb_strtoupper($str) ^ $str);
11039
        }
11040
11041 4
        return (string) (self::strtolower($str, $encoding) ^ self::strtoupper($str, $encoding) ^ $str);
11042
    }
11043
11044
    /**
11045
     * Checks whether symfony-polyfills are used.
11046
     *
11047
     * @return bool
11048
     *              <strong>true</strong> if in use, <strong>false</strong> otherwise
11049
     */
11050
    public static function symfony_polyfill_used(): bool
11051
    {
11052
        // init
11053
        $return = false;
11054
11055
        $return_tmp = \extension_loaded('mbstring');
11056
        if ($return_tmp === false && \function_exists('mb_strlen')) {
11057
            $return = true;
11058
        }
11059
11060
        $return_tmp = \extension_loaded('iconv');
11061
        if ($return_tmp === false && \function_exists('iconv')) {
11062
            $return = true;
11063
        }
11064
11065
        return $return;
11066
    }
11067
11068
    /**
11069
     * @param string $str
11070
     * @param int    $tab_length
11071
     *
11072
     * @return string
11073
     */
11074
    public static function tabs_to_spaces(string $str, int $tab_length = 4): string
11075
    {
11076 6
        if ($tab_length === 4) {
11077 3
            $spaces = '    ';
11078 3
        } elseif ($tab_length === 2) {
11079 1
            $spaces = '  ';
11080
        } else {
11081 2
            $spaces = \str_repeat(' ', $tab_length);
11082
        }
11083
11084 6
        return \str_replace("\t", $spaces, $str);
11085
    }
11086
11087
    /**
11088
     * Converts the first character of each word in the string to uppercase
11089
     * and all other chars to lowercase.
11090
     *
11091
     * @param string      $str                           <p>The input string.</p>
11092
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
11093
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
11094
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
11095
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
11096
     *
11097
     * @return string
11098
     *                <p>A string with all characters of $str being title-cased.</p>
11099
     */
11100
    public static function titlecase(
11101
        string $str,
11102
        string $encoding = 'UTF-8',
11103
        bool $clean_utf8 = false,
11104
        string $lang = null,
11105
        bool $try_to_keep_the_string_length = false
11106
    ): string {
11107 5
        if ($clean_utf8 === true) {
11108
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
11109
            // if invalid characters are found in $haystack before $needle
11110
            $str = self::clean($str);
11111
        }
11112
11113 5
        if ($lang === null && $try_to_keep_the_string_length === false) {
11114 5
            if ($encoding === 'UTF-8') {
11115 3
                return \mb_convert_case($str, \MB_CASE_TITLE);
11116
            }
11117
11118 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
11119
11120 2
            return \mb_convert_case($str, \MB_CASE_TITLE, $encoding);
11121
        }
11122
11123
        return self::str_titleize(
11124
            $str,
11125
            null,
11126
            $encoding,
11127
            false,
11128
            $lang,
11129
            $try_to_keep_the_string_length,
11130
            false
11131
        );
11132
    }
11133
11134
    /**
11135
     * alias for "UTF8::to_ascii()"
11136
     *
11137
     * @param string $str
11138
     * @param string $subst_chr
11139
     * @param bool   $strict
11140
     *
11141
     * @return string
11142
     *
11143
     * @see UTF8::to_ascii()
11144
     * @deprecated <p>please use "UTF8::to_ascii()"</p>
11145
     */
11146
    public static function toAscii(
11147
        string $str,
11148
        string $subst_chr = '?',
11149
        bool $strict = false
11150
    ): string {
11151 7
        return self::to_ascii($str, $subst_chr, $strict);
11152
    }
11153
11154
    /**
11155
     * alias for "UTF8::to_iso8859()"
11156
     *
11157
     * @param string|string[] $str
11158
     *
11159
     * @return string|string[]
11160
     *
11161
     * @see UTF8::to_iso8859()
11162
     * @deprecated <p>please use "UTF8::to_iso8859()"</p>
11163
     */
11164
    public static function toIso8859($str)
11165
    {
11166 2
        return self::to_iso8859($str);
11167
    }
11168
11169
    /**
11170
     * alias for "UTF8::to_latin1()"
11171
     *
11172
     * @param string|string[] $str
11173
     *
11174
     * @return string|string[]
11175
     *
11176
     * @see UTF8::to_iso8859()
11177
     * @deprecated <p>please use "UTF8::to_iso8859()"</p>
11178
     */
11179
    public static function toLatin1($str)
11180
    {
11181 2
        return self::to_iso8859($str);
11182
    }
11183
11184
    /**
11185
     * alias for "UTF8::to_utf8()"
11186
     *
11187
     * @param string|string[] $str
11188
     *
11189
     * @return string|string[]
11190
     *
11191
     * @see UTF8::to_utf8()
11192
     * @deprecated <p>please use "UTF8::to_utf8()"</p>
11193
     */
11194
    public static function toUTF8($str)
11195
    {
11196 2
        return self::to_utf8($str);
11197
    }
11198
11199
    /**
11200
     * Convert a string into ASCII.
11201
     *
11202
     * @param string $str     <p>The input string.</p>
11203
     * @param string $unknown [optional] <p>Character use if character unknown. (default is ?)</p>
11204
     * @param bool   $strict  [optional] <p>Use "transliterator_transliterate()" from PHP-Intl | WARNING: bad
11205
     *                        performance</p>
11206
     *
11207
     * @return string
11208
     */
11209
    public static function to_ascii(
11210
        string $str,
11211
        string $unknown = '?',
11212
        bool $strict = false
11213
    ): string {
11214 37
        return ASCII::to_transliterate($str, $unknown, $strict);
11215
    }
11216
11217
    /**
11218
     * @param mixed $str
11219
     *
11220
     * @return bool
11221
     */
11222
    public static function to_boolean($str): bool
11223
    {
11224
        // init
11225 19
        $str = (string) $str;
11226
11227 19
        if ($str === '') {
11228 2
            return false;
11229
        }
11230
11231
        // Info: http://php.net/manual/en/filter.filters.validate.php
11232
        $map = [
11233 17
            'true'  => true,
11234
            '1'     => true,
11235
            'on'    => true,
11236
            'yes'   => true,
11237
            'false' => false,
11238
            '0'     => false,
11239
            'off'   => false,
11240
            'no'    => false,
11241
        ];
11242
11243 17
        if (isset($map[$str])) {
11244 11
            return $map[$str];
11245
        }
11246
11247 6
        $key = \strtolower($str);
11248 6
        if (isset($map[$key])) {
11249 2
            return $map[$key];
11250
        }
11251
11252 4
        if (\is_numeric($str)) {
11253 2
            return ((float) $str + 0) > 0;
11254
        }
11255
11256 2
        return (bool) \trim($str);
11257
    }
11258
11259
    /**
11260
     * Convert given string to safe filename (and keep string case).
11261
     *
11262
     * @param string $str
11263
     * @param bool   $use_transliterate No transliteration, conversion etc. is done by default - unsafe characters are
11264
     *                                  simply replaced with hyphen.
11265
     * @param string $fallback_char
11266
     *
11267
     * @return string
11268
     */
11269
    public static function to_filename(
11270
        string $str,
11271
        bool $use_transliterate = false,
11272
        string $fallback_char = '-'
11273
    ): string {
11274 1
        return ASCII::to_filename(
11275 1
            $str,
11276 1
            $use_transliterate,
11277 1
            $fallback_char
11278
        );
11279
    }
11280
11281
    /**
11282
     * Convert a string into "ISO-8859"-encoding (Latin-1).
11283
     *
11284
     * @param string|string[] $str
11285
     *
11286
     * @return string|string[]
11287
     */
11288
    public static function to_iso8859($str)
11289
    {
11290 8
        if (\is_array($str) === true) {
11291 2
            foreach ($str as $k => &$v) {
11292 2
                $v = self::to_iso8859($v);
11293
            }
11294
11295 2
            return $str;
11296
        }
11297
11298 8
        $str = (string) $str;
11299 8
        if ($str === '') {
11300 2
            return '';
11301
        }
11302
11303 8
        return self::utf8_decode($str);
11304
    }
11305
11306
    /**
11307
     * alias for "UTF8::to_iso8859()"
11308
     *
11309
     * @param string|string[] $str
11310
     *
11311
     * @return string|string[]
11312
     *
11313
     * @see UTF8::to_iso8859()
11314
     * @deprecated <p>please use "UTF8::to_iso8859()"</p>
11315
     */
11316
    public static function to_latin1($str)
11317
    {
11318 2
        return self::to_iso8859($str);
11319
    }
11320
11321
    /**
11322
     * This function leaves UTF-8 characters alone, while converting almost all non-UTF8 to UTF8.
11323
     *
11324
     * <ul>
11325
     * <li>It decode UTF-8 codepoints and Unicode escape sequences.</li>
11326
     * <li>It assumes that the encoding of the original string is either WINDOWS-1252 or ISO-8859.</li>
11327
     * <li>WARNING: It does not remove invalid UTF-8 characters, so you maybe need to use "UTF8::clean()" for this
11328
     * case.</li>
11329
     * </ul>
11330
     *
11331
     * @param string|string[] $str                        <p>Any string or array.</p>
11332
     * @param bool            $decode_html_entity_to_utf8 <p>Set to true, if you need to decode html-entities.</p>
11333
     *
11334
     * @return string|string[] the UTF-8 encoded string
11335
     */
11336
    public static function to_utf8($str, bool $decode_html_entity_to_utf8 = false)
11337
    {
11338 42
        if (\is_array($str) === true) {
11339 4
            foreach ($str as $k => &$v) {
11340 4
                $v = self::to_utf8($v, $decode_html_entity_to_utf8);
11341
            }
11342
11343 4
            return $str;
11344
        }
11345
11346 42
        $str = (string) $str;
11347 42
        if ($str === '') {
11348 7
            return $str;
11349
        }
11350
11351 42
        $max = \strlen($str);
11352 42
        $buf = '';
11353
11354 42
        for ($i = 0; $i < $max; ++$i) {
11355 42
            $c1 = $str[$i];
11356
11357 42
            if ($c1 >= "\xC0") { // should be converted to UTF8, if it's not UTF8 already
11358
11359 38
                if ($c1 <= "\xDF") { // looks like 2 bytes UTF8
11360
11361 35
                    $c2 = $i + 1 >= $max ? "\x00" : $str[$i + 1];
11362
11363 35
                    if ($c2 >= "\x80" && $c2 <= "\xBF") { // yeah, almost sure it's UTF8 already
11364 21
                        $buf .= $c1 . $c2;
11365 21
                        ++$i;
11366
                    } else { // not valid UTF8 - convert it
11367 35
                        $buf .= self::to_utf8_convert_helper($c1);
11368
                    }
11369 35
                } elseif ($c1 >= "\xE0" && $c1 <= "\xEF") { // looks like 3 bytes UTF8
11370
11371 34
                    $c2 = $i + 1 >= $max ? "\x00" : $str[$i + 1];
11372 34
                    $c3 = $i + 2 >= $max ? "\x00" : $str[$i + 2];
11373
11374 34
                    if ($c2 >= "\x80" && $c2 <= "\xBF" && $c3 >= "\x80" && $c3 <= "\xBF") { // yeah, almost sure it's UTF8 already
11375 16
                        $buf .= $c1 . $c2 . $c3;
11376 16
                        $i += 2;
11377
                    } else { // not valid UTF8 - convert it
11378 34
                        $buf .= self::to_utf8_convert_helper($c1);
11379
                    }
11380 27
                } elseif ($c1 >= "\xF0" && $c1 <= "\xF7") { // looks like 4 bytes UTF8
11381
11382 27
                    $c2 = $i + 1 >= $max ? "\x00" : $str[$i + 1];
11383 27
                    $c3 = $i + 2 >= $max ? "\x00" : $str[$i + 2];
11384 27
                    $c4 = $i + 3 >= $max ? "\x00" : $str[$i + 3];
11385
11386 27
                    if ($c2 >= "\x80" && $c2 <= "\xBF" && $c3 >= "\x80" && $c3 <= "\xBF" && $c4 >= "\x80" && $c4 <= "\xBF") { // yeah, almost sure it's UTF8 already
11387 9
                        $buf .= $c1 . $c2 . $c3 . $c4;
11388 9
                        $i += 3;
11389
                    } else { // not valid UTF8 - convert it
11390 27
                        $buf .= self::to_utf8_convert_helper($c1);
11391
                    }
11392
                } else { // doesn't look like UTF8, but should be converted
11393
11394 38
                    $buf .= self::to_utf8_convert_helper($c1);
11395
                }
11396 39
            } elseif (($c1 & "\xC0") === "\x80") { // needs conversion
11397
11398 4
                $buf .= self::to_utf8_convert_helper($c1);
11399
            } else { // it doesn't need conversion
11400
11401 39
                $buf .= $c1;
11402
            }
11403
        }
11404
11405
        // decode unicode escape sequences + unicode surrogate pairs
11406 42
        $buf = \preg_replace_callback(
11407 42
            '/\\\\u([dD][89abAB][0-9a-fA-F]{2})\\\\u([dD][cdefCDEF][\da-fA-F]{2})|\\\\u([0-9a-fA-F]{4})/',
11408
            /**
11409
             * @param array $matches
11410
             *
11411
             * @return string
11412
             */
11413
            static function (array $matches): string {
11414 12
                if (isset($matches[3])) {
11415 12
                    $cp = (int) \hexdec($matches[3]);
11416
                } else {
11417
                    // http://unicode.org/faq/utf_bom.html#utf16-4
11418
                    $cp = ((int) \hexdec($matches[1]) << 10)
11419
                          + (int) \hexdec($matches[2])
11420
                          + 0x10000
11421
                          - (0xD800 << 10)
11422
                          - 0xDC00;
11423
                }
11424
11425
                // https://github.com/php/php-src/blob/php-7.3.2/ext/standard/html.c#L471
11426
                //
11427
                // php_utf32_utf8(unsigned char *buf, unsigned k)
11428
11429 12
                if ($cp < 0x80) {
11430 8
                    return (string) self::chr($cp);
11431
                }
11432
11433 9
                if ($cp < 0xA0) {
11434
                    /** @noinspection UnnecessaryCastingInspection */
11435
                    return (string) self::chr(0xC0 | $cp >> 6) . (string) self::chr(0x80 | $cp & 0x3F);
11436
                }
11437
11438 9
                return self::decimal_to_chr($cp);
11439 42
            },
11440 42
            $buf
11441
        );
11442
11443 42
        if ($buf === null) {
11444
            return '';
11445
        }
11446
11447
        // decode UTF-8 codepoints
11448 42
        if ($decode_html_entity_to_utf8 === true) {
11449 2
            $buf = self::html_entity_decode($buf);
11450
        }
11451
11452 42
        return $buf;
11453
    }
11454
11455
    /**
11456
     * Strip whitespace or other characters from the beginning and end of a UTF-8 string.
11457
     *
11458
     * INFO: This is slower then "trim()"
11459
     *
11460
     * We can only use the original-function, if we use <= 7-Bit in the string / chars
11461
     * but the check for ASCII (7-Bit) cost more time, then we can safe here.
11462
     *
11463
     * @param string      $str   <p>The string to be trimmed</p>
11464
     * @param string|null $chars [optional] <p>Optional characters to be stripped</p>
11465
     *
11466
     * @return string the trimmed string
11467
     */
11468
    public static function trim(string $str = '', string $chars = null): string
11469
    {
11470 56
        if ($str === '') {
11471 9
            return '';
11472
        }
11473
11474 49
        if (self::$SUPPORT['mbstring'] === true) {
11475 49
            if ($chars) {
11476
                /** @noinspection PregQuoteUsageInspection */
11477 27
                $chars = \preg_quote($chars);
11478 27
                $pattern = "^[${chars}]+|[${chars}]+\$";
11479
            } else {
11480 22
                $pattern = '^[\\s]+|[\\s]+$';
11481
            }
11482
11483
            /** @noinspection PhpComposerExtensionStubsInspection */
11484 49
            return (string) \mb_ereg_replace($pattern, '', $str);
11485
        }
11486
11487 8
        if ($chars) {
11488
            $chars = \preg_quote($chars, '/');
11489
            $pattern = "^[${chars}]+|[${chars}]+\$";
11490
        } else {
11491 8
            $pattern = '^[\\s]+|[\\s]+$';
11492
        }
11493
11494 8
        return self::regex_replace($str, $pattern, '', '', '/');
11495
    }
11496
11497
    /**
11498
     * Makes string's first char uppercase.
11499
     *
11500
     * @param string      $str                           <p>The input string.</p>
11501
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
11502
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
11503
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
11504
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
11505
     *
11506
     * @return string the resulting string
11507
     */
11508
    public static function ucfirst(
11509
        string $str,
11510
        string $encoding = 'UTF-8',
11511
        bool $clean_utf8 = false,
11512
        string $lang = null,
11513
        bool $try_to_keep_the_string_length = false
11514
    ): string {
11515 69
        if ($str === '') {
11516 3
            return '';
11517
        }
11518
11519 68
        if ($clean_utf8 === true) {
11520
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
11521
            // if invalid characters are found in $haystack before $needle
11522 1
            $str = self::clean($str);
11523
        }
11524
11525 68
        $use_mb_functions = $lang === null && $try_to_keep_the_string_length === false;
11526
11527 68
        if ($encoding === 'UTF-8') {
11528 22
            $str_part_two = (string) \mb_substr($str, 1);
11529
11530 22
            if ($use_mb_functions === true) {
11531 22
                $str_part_one = \mb_strtoupper(
11532 22
                    (string) \mb_substr($str, 0, 1)
11533
                );
11534
            } else {
11535
                $str_part_one = self::strtoupper(
11536
                    (string) \mb_substr($str, 0, 1),
11537
                    $encoding,
11538
                    false,
11539
                    $lang,
11540 22
                    $try_to_keep_the_string_length
11541
                );
11542
            }
11543
        } else {
11544 47
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
11545
11546 47
            $str_part_two = (string) self::substr($str, 1, null, $encoding);
11547
11548 47
            if ($use_mb_functions === true) {
11549 47
                $str_part_one = \mb_strtoupper(
11550 47
                    (string) \mb_substr($str, 0, 1, $encoding),
11551 47
                    $encoding
11552
                );
11553
            } else {
11554
                $str_part_one = self::strtoupper(
11555
                    (string) self::substr($str, 0, 1, $encoding),
11556
                    $encoding,
11557
                    false,
11558
                    $lang,
11559
                    $try_to_keep_the_string_length
11560
                );
11561
            }
11562
        }
11563
11564 68
        return $str_part_one . $str_part_two;
11565
    }
11566
11567
    /**
11568
     * alias for "UTF8::ucfirst()"
11569
     *
11570
     * @param string $str
11571
     * @param string $encoding
11572
     * @param bool   $clean_utf8
11573
     *
11574
     * @return string
11575
     *
11576
     * @see UTF8::ucfirst()
11577
     * @deprecated <p>please use "UTF8::ucfirst()"</p>
11578
     */
11579
    public static function ucword(
11580
        string $str,
11581
        string $encoding = 'UTF-8',
11582
        bool $clean_utf8 = false
11583
    ): string {
11584 1
        return self::ucfirst($str, $encoding, $clean_utf8);
11585
    }
11586
11587
    /**
11588
     * Uppercase for all words in the string.
11589
     *
11590
     * @param string   $str        <p>The input string.</p>
11591
     * @param string[] $exceptions [optional] <p>Exclusion for some words.</p>
11592
     * @param string   $char_list  [optional] <p>Additional chars that contains to words and do not start a new
11593
     *                             word.</p>
11594
     * @param string   $encoding   [optional] <p>Set the charset.</p>
11595
     * @param bool     $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
11596
     *
11597
     * @return string
11598
     */
11599
    public static function ucwords(
11600
        string $str,
11601
        array $exceptions = [],
11602
        string $char_list = '',
11603
        string $encoding = 'UTF-8',
11604
        bool $clean_utf8 = false
11605
    ): string {
11606 8
        if (!$str) {
11607 2
            return '';
11608
        }
11609
11610
        // INFO: mb_convert_case($str, MB_CASE_TITLE);
11611
        // -> MB_CASE_TITLE didn't only uppercase the first letter, it also lowercase all other letters
11612
11613 7
        if ($clean_utf8 === true) {
11614
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
11615
            // if invalid characters are found in $haystack before $needle
11616 1
            $str = self::clean($str);
11617
        }
11618
11619 7
        $use_php_default_functions = !(bool) ($char_list . \implode('', $exceptions));
11620
11621
        if (
11622 7
            $use_php_default_functions === true
11623
            &&
11624 7
            ASCII::is_ascii($str) === true
11625
        ) {
11626
            return \ucwords($str);
11627
        }
11628
11629 7
        $words = self::str_to_words($str, $char_list);
11630 7
        $use_exceptions = $exceptions !== [];
11631
11632 7
        $words_str = '';
11633 7
        foreach ($words as &$word) {
11634 7
            if (!$word) {
11635 7
                continue;
11636
            }
11637
11638
            if (
11639 7
                $use_exceptions === false
11640
                ||
11641 7
                !\in_array($word, $exceptions, true)
11642
            ) {
11643 7
                $words_str .= self::ucfirst($word, $encoding);
11644
            } else {
11645 7
                $words_str .= $word;
11646
            }
11647
        }
11648
11649 7
        return $words_str;
11650
    }
11651
11652
    /**
11653
     * Multi decode HTML entity + fix urlencoded-win1252-chars.
11654
     *
11655
     * e.g:
11656
     * 'test+test'                     => 'test test'
11657
     * 'D&#252;sseldorf'               => 'Düsseldorf'
11658
     * 'D%FCsseldorf'                  => 'Düsseldorf'
11659
     * 'D&#xFC;sseldorf'               => 'Düsseldorf'
11660
     * 'D%26%23xFC%3Bsseldorf'         => 'Düsseldorf'
11661
     * 'Düsseldorf'                   => 'Düsseldorf'
11662
     * 'D%C3%BCsseldorf'               => 'Düsseldorf'
11663
     * 'D%C3%83%C2%BCsseldorf'         => 'Düsseldorf'
11664
     * 'D%25C3%2583%25C2%25BCsseldorf' => 'Düsseldorf'
11665
     *
11666
     * @param string $str          <p>The input string.</p>
11667
     * @param bool   $multi_decode <p>Decode as often as possible.</p>
11668
     *
11669
     * @return string
11670
     */
11671
    public static function urldecode(string $str, bool $multi_decode = true): string
11672
    {
11673 4
        if ($str === '') {
11674 3
            return '';
11675
        }
11676
11677
        if (
11678 4
            \strpos($str, '&') === false
11679
            &&
11680 4
            \strpos($str, '%') === false
11681
            &&
11682 4
            \strpos($str, '+') === false
11683
            &&
11684 4
            \strpos($str, '\u') === false
11685
        ) {
11686 3
            return self::fix_simple_utf8($str);
11687
        }
11688
11689 4
        $str = self::urldecode_unicode_helper($str);
11690
11691
        do {
11692 4
            $str_compare = $str;
11693
11694
            /**
11695
             * @psalm-suppress PossiblyInvalidArgument
11696
             */
11697 4
            $str = self::fix_simple_utf8(
11698 4
                \urldecode(
11699 4
                    self::html_entity_decode(
11700 4
                        self::to_utf8($str),
11701 4
                        \ENT_QUOTES | \ENT_HTML5
11702
                    )
11703
                )
11704
            );
11705 4
        } while ($multi_decode === true && $str_compare !== $str);
11706
11707 4
        return $str;
11708
    }
11709
11710
    /**
11711
     * Return a array with "urlencoded"-win1252 -> UTF-8
11712
     *
11713
     * @return string[]
11714
     *
11715
     * @deprecated <p>please use the "UTF8::urldecode()" function to decode a string</p>
11716
     */
11717
    public static function urldecode_fix_win1252_chars(): array
11718
    {
11719
        return [
11720 2
            '%20' => ' ',
11721
            '%21' => '!',
11722
            '%22' => '"',
11723
            '%23' => '#',
11724
            '%24' => '$',
11725
            '%25' => '%',
11726
            '%26' => '&',
11727
            '%27' => "'",
11728
            '%28' => '(',
11729
            '%29' => ')',
11730
            '%2A' => '*',
11731
            '%2B' => '+',
11732
            '%2C' => ',',
11733
            '%2D' => '-',
11734
            '%2E' => '.',
11735
            '%2F' => '/',
11736
            '%30' => '0',
11737
            '%31' => '1',
11738
            '%32' => '2',
11739
            '%33' => '3',
11740
            '%34' => '4',
11741
            '%35' => '5',
11742
            '%36' => '6',
11743
            '%37' => '7',
11744
            '%38' => '8',
11745
            '%39' => '9',
11746
            '%3A' => ':',
11747
            '%3B' => ';',
11748
            '%3C' => '<',
11749
            '%3D' => '=',
11750
            '%3E' => '>',
11751
            '%3F' => '?',
11752
            '%40' => '@',
11753
            '%41' => 'A',
11754
            '%42' => 'B',
11755
            '%43' => 'C',
11756
            '%44' => 'D',
11757
            '%45' => 'E',
11758
            '%46' => 'F',
11759
            '%47' => 'G',
11760
            '%48' => 'H',
11761
            '%49' => 'I',
11762
            '%4A' => 'J',
11763
            '%4B' => 'K',
11764
            '%4C' => 'L',
11765
            '%4D' => 'M',
11766
            '%4E' => 'N',
11767
            '%4F' => 'O',
11768
            '%50' => 'P',
11769
            '%51' => 'Q',
11770
            '%52' => 'R',
11771
            '%53' => 'S',
11772
            '%54' => 'T',
11773
            '%55' => 'U',
11774
            '%56' => 'V',
11775
            '%57' => 'W',
11776
            '%58' => 'X',
11777
            '%59' => 'Y',
11778
            '%5A' => 'Z',
11779
            '%5B' => '[',
11780
            '%5C' => '\\',
11781
            '%5D' => ']',
11782
            '%5E' => '^',
11783
            '%5F' => '_',
11784
            '%60' => '`',
11785
            '%61' => 'a',
11786
            '%62' => 'b',
11787
            '%63' => 'c',
11788
            '%64' => 'd',
11789
            '%65' => 'e',
11790
            '%66' => 'f',
11791
            '%67' => 'g',
11792
            '%68' => 'h',
11793
            '%69' => 'i',
11794
            '%6A' => 'j',
11795
            '%6B' => 'k',
11796
            '%6C' => 'l',
11797
            '%6D' => 'm',
11798
            '%6E' => 'n',
11799
            '%6F' => 'o',
11800
            '%70' => 'p',
11801
            '%71' => 'q',
11802
            '%72' => 'r',
11803
            '%73' => 's',
11804
            '%74' => 't',
11805
            '%75' => 'u',
11806
            '%76' => 'v',
11807
            '%77' => 'w',
11808
            '%78' => 'x',
11809
            '%79' => 'y',
11810
            '%7A' => 'z',
11811
            '%7B' => '{',
11812
            '%7C' => '|',
11813
            '%7D' => '}',
11814
            '%7E' => '~',
11815
            '%7F' => '',
11816
            '%80' => '`',
11817
            '%81' => '',
11818
            '%82' => '‚',
11819
            '%83' => 'ƒ',
11820
            '%84' => '„',
11821
            '%85' => '…',
11822
            '%86' => '†',
11823
            '%87' => '‡',
11824
            '%88' => 'ˆ',
11825
            '%89' => '‰',
11826
            '%8A' => 'Š',
11827
            '%8B' => '‹',
11828
            '%8C' => 'Œ',
11829
            '%8D' => '',
11830
            '%8E' => 'Ž',
11831
            '%8F' => '',
11832
            '%90' => '',
11833
            '%91' => '‘',
11834
            '%92' => '’',
11835
            '%93' => '“',
11836
            '%94' => '”',
11837
            '%95' => '•',
11838
            '%96' => '–',
11839
            '%97' => '—',
11840
            '%98' => '˜',
11841
            '%99' => '™',
11842
            '%9A' => 'š',
11843
            '%9B' => '›',
11844
            '%9C' => 'œ',
11845
            '%9D' => '',
11846
            '%9E' => 'ž',
11847
            '%9F' => 'Ÿ',
11848
            '%A0' => '',
11849
            '%A1' => '¡',
11850
            '%A2' => '¢',
11851
            '%A3' => '£',
11852
            '%A4' => '¤',
11853
            '%A5' => '¥',
11854
            '%A6' => '¦',
11855
            '%A7' => '§',
11856
            '%A8' => '¨',
11857
            '%A9' => '©',
11858
            '%AA' => 'ª',
11859
            '%AB' => '«',
11860
            '%AC' => '¬',
11861
            '%AD' => '',
11862
            '%AE' => '®',
11863
            '%AF' => '¯',
11864
            '%B0' => '°',
11865
            '%B1' => '±',
11866
            '%B2' => '²',
11867
            '%B3' => '³',
11868
            '%B4' => '´',
11869
            '%B5' => 'µ',
11870
            '%B6' => '¶',
11871
            '%B7' => '·',
11872
            '%B8' => '¸',
11873
            '%B9' => '¹',
11874
            '%BA' => 'º',
11875
            '%BB' => '»',
11876
            '%BC' => '¼',
11877
            '%BD' => '½',
11878
            '%BE' => '¾',
11879
            '%BF' => '¿',
11880
            '%C0' => 'À',
11881
            '%C1' => 'Á',
11882
            '%C2' => 'Â',
11883
            '%C3' => 'Ã',
11884
            '%C4' => 'Ä',
11885
            '%C5' => 'Å',
11886
            '%C6' => 'Æ',
11887
            '%C7' => 'Ç',
11888
            '%C8' => 'È',
11889
            '%C9' => 'É',
11890
            '%CA' => 'Ê',
11891
            '%CB' => 'Ë',
11892
            '%CC' => 'Ì',
11893
            '%CD' => 'Í',
11894
            '%CE' => 'Î',
11895
            '%CF' => 'Ï',
11896
            '%D0' => 'Ð',
11897
            '%D1' => 'Ñ',
11898
            '%D2' => 'Ò',
11899
            '%D3' => 'Ó',
11900
            '%D4' => 'Ô',
11901
            '%D5' => 'Õ',
11902
            '%D6' => 'Ö',
11903
            '%D7' => '×',
11904
            '%D8' => 'Ø',
11905
            '%D9' => 'Ù',
11906
            '%DA' => 'Ú',
11907
            '%DB' => 'Û',
11908
            '%DC' => 'Ü',
11909
            '%DD' => 'Ý',
11910
            '%DE' => 'Þ',
11911
            '%DF' => 'ß',
11912
            '%E0' => 'à',
11913
            '%E1' => 'á',
11914
            '%E2' => 'â',
11915
            '%E3' => 'ã',
11916
            '%E4' => 'ä',
11917
            '%E5' => 'å',
11918
            '%E6' => 'æ',
11919
            '%E7' => 'ç',
11920
            '%E8' => 'è',
11921
            '%E9' => 'é',
11922
            '%EA' => 'ê',
11923
            '%EB' => 'ë',
11924
            '%EC' => 'ì',
11925
            '%ED' => 'í',
11926
            '%EE' => 'î',
11927
            '%EF' => 'ï',
11928
            '%F0' => 'ð',
11929
            '%F1' => 'ñ',
11930
            '%F2' => 'ò',
11931
            '%F3' => 'ó',
11932
            '%F4' => 'ô',
11933
            '%F5' => 'õ',
11934
            '%F6' => 'ö',
11935
            '%F7' => '÷',
11936
            '%F8' => 'ø',
11937
            '%F9' => 'ù',
11938
            '%FA' => 'ú',
11939
            '%FB' => 'û',
11940
            '%FC' => 'ü',
11941
            '%FD' => 'ý',
11942
            '%FE' => 'þ',
11943
            '%FF' => 'ÿ',
11944
        ];
11945
    }
11946
11947
    /**
11948
     * Decodes a UTF-8 string to ISO-8859-1.
11949
     *
11950
     * @param string $str             <p>The input string.</p>
11951
     * @param bool   $keep_utf8_chars
11952
     *
11953
     * @return string
11954
     */
11955
    public static function utf8_decode(string $str, bool $keep_utf8_chars = false): string
11956
    {
11957 14
        if ($str === '') {
11958 6
            return '';
11959
        }
11960
11961
        // save for later comparision
11962 14
        $str_backup = $str;
11963 14
        $len = \strlen($str);
11964
11965 14
        if (self::$ORD === null) {
11966
            self::$ORD = self::getData('ord');
11967
        }
11968
11969 14
        if (self::$CHR === null) {
11970
            self::$CHR = self::getData('chr');
11971
        }
11972
11973 14
        $no_char_found = '?';
11974
        /** @noinspection ForeachInvariantsInspection */
11975 14
        for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) {
11976 14
            switch ($str[$i] & "\xF0") {
11977 14
                case "\xC0":
11978 13
                case "\xD0":
11979 13
                    $c = (self::$ORD[$str[$i] & "\x1F"] << 6) | self::$ORD[$str[++$i] & "\x3F"];
11980 13
                    $str[$j] = $c < 256 ? self::$CHR[$c] : $no_char_found;
11981
11982 13
                    break;
11983
11984
                /** @noinspection PhpMissingBreakStatementInspection */
11985 13
                case "\xF0":
11986
                    ++$i;
11987
11988
                // no break
11989
11990 13
                case "\xE0":
11991 11
                    $str[$j] = $no_char_found;
11992 11
                    $i += 2;
11993
11994 11
                    break;
11995
11996
                default:
11997 12
                    $str[$j] = $str[$i];
11998
            }
11999
        }
12000
12001
        /** @var false|string $return - needed for PhpStan (stubs error) */
12002 14
        $return = \substr($str, 0, $j);
12003 14
        if ($return === false) {
12004
            $return = '';
12005
        }
12006
12007
        if (
12008 14
            $keep_utf8_chars === true
12009
            &&
12010 14
            (int) self::strlen($return) >= (int) self::strlen($str_backup)
12011
        ) {
12012 2
            return $str_backup;
12013
        }
12014
12015 14
        return $return;
12016
    }
12017
12018
    /**
12019
     * Encodes an ISO-8859-1 string to UTF-8.
12020
     *
12021
     * @param string $str <p>The input string.</p>
12022
     *
12023
     * @return string
12024
     */
12025
    public static function utf8_encode(string $str): string
12026
    {
12027 14
        if ($str === '') {
12028 14
            return '';
12029
        }
12030
12031
        /** @var false|string $str - the polyfill maybe return false */
12032 14
        $str = \utf8_encode($str);
0 ignored issues
show
Bug introduced by
It seems like $str can also be of type false; however, parameter $data of utf8_encode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

12032
        $str = \utf8_encode(/** @scrutinizer ignore-type */ $str);
Loading history...
12033
12034
        /** @noinspection CallableParameterUseCaseInTypeContextInspection */
12035
        /** @psalm-suppress TypeDoesNotContainType */
12036 14
        if ($str === false) {
12037
            return '';
12038
        }
12039
12040 14
        return $str;
12041
    }
12042
12043
    /**
12044
     * fix -> utf8-win1252 chars
12045
     *
12046
     * @param string $str <p>The input string.</p>
12047
     *
12048
     * @return string
12049
     *
12050
     * @deprecated <p>please use "UTF8::fix_simple_utf8()"</p>
12051
     */
12052
    public static function utf8_fix_win1252_chars(string $str): string
12053
    {
12054 2
        return self::fix_simple_utf8($str);
12055
    }
12056
12057
    /**
12058
     * Returns an array with all utf8 whitespace characters.
12059
     *
12060
     * @see http://www.bogofilter.org/pipermail/bogofilter/2003-March/001889.html
12061
     *
12062
     * @return string[]
12063
     *                  An array with all known whitespace characters as values and the type of whitespace as keys
12064
     *                  as defined in above URL
12065
     */
12066
    public static function whitespace_table(): array
12067
    {
12068 2
        return self::$WHITESPACE_TABLE;
12069
    }
12070
12071
    /**
12072
     * Limit the number of words in a string.
12073
     *
12074
     * @param string $str        <p>The input string.</p>
12075
     * @param int    $limit      <p>The limit of words as integer.</p>
12076
     * @param string $str_add_on <p>Replacement for the striped string.</p>
12077
     *
12078
     * @return string
12079
     */
12080
    public static function words_limit(
12081
        string $str,
12082
        int $limit = 100,
12083
        string $str_add_on = '…'
12084
    ): string {
12085 2
        if ($str === '' || $limit < 1) {
12086 2
            return '';
12087
        }
12088
12089 2
        \preg_match('/^\\s*+(?:[^\\s]++\\s*+){1,' . $limit . '}/u', $str, $matches);
12090
12091
        if (
12092 2
            !isset($matches[0])
12093
            ||
12094 2
            \mb_strlen($str) === (int) \mb_strlen($matches[0])
12095
        ) {
12096 2
            return $str;
12097
        }
12098
12099 2
        return \rtrim($matches[0]) . $str_add_on;
12100
    }
12101
12102
    /**
12103
     * Wraps a string to a given number of characters
12104
     *
12105
     * @see http://php.net/manual/en/function.wordwrap.php
12106
     *
12107
     * @param string $str   <p>The input string.</p>
12108
     * @param int    $width [optional] <p>The column width.</p>
12109
     * @param string $break [optional] <p>The line is broken using the optional break parameter.</p>
12110
     * @param bool   $cut   [optional] <p>
12111
     *                      If the cut is set to true, the string is
12112
     *                      always wrapped at or before the specified width. So if you have
12113
     *                      a word that is larger than the given width, it is broken apart.
12114
     *                      </p>
12115
     *
12116
     * @return string
12117
     *                <p>The given string wrapped at the specified column.</p>
12118
     */
12119
    public static function wordwrap(
12120
        string $str,
12121
        int $width = 75,
12122
        string $break = "\n",
12123
        bool $cut = false
12124
    ): string {
12125 12
        if ($str === '' || $break === '') {
12126 4
            return '';
12127
        }
12128
12129 10
        $str_split = \explode($break, $str);
12130 10
        if ($str_split === false) {
12131
            return '';
12132
        }
12133
12134 10
        $chars = [];
12135 10
        $word_split = '';
12136 10
        foreach ($str_split as $i => $i_value) {
12137 10
            if ($i) {
12138 3
                $chars[] = $break;
12139 3
                $word_split .= '#';
12140
            }
12141
12142 10
            foreach (self::str_split($i_value) as $c) {
12143 10
                $chars[] = $c;
12144 10
                if ($c === ' ') {
12145 3
                    $word_split .= ' ';
12146
                } else {
12147 10
                    $word_split .= '?';
12148
                }
12149
            }
12150
        }
12151
12152 10
        $str_return = '';
12153 10
        $j = 0;
12154 10
        $b = -1;
12155 10
        $i = -1;
12156 10
        $word_split = \wordwrap($word_split, $width, '#', $cut);
12157
12158 10
        $max = \mb_strlen($word_split);
12159 10
        while (($b = \mb_strpos($word_split, '#', $b + 1)) !== false) {
12160 8
            for (++$i; $i < $b; ++$i) {
12161 8
                $str_return .= $chars[$j];
12162 8
                unset($chars[$j++]);
12163
12164
                // prevent endless loop, e.g. if there is a error in the "mb_*" polyfill
12165 8
                if ($i > $max) {
12166
                    break 2;
12167
                }
12168
            }
12169
12170
            if (
12171 8
                $break === $chars[$j]
12172
                ||
12173 8
                $chars[$j] === ' '
12174
            ) {
12175 5
                unset($chars[$j++]);
12176
            }
12177
12178 8
            $str_return .= $break;
12179
12180
            // prevent endless loop, e.g. if there is a error in the "mb_*" polyfill
12181 8
            if ($b > $max) {
12182
                break;
12183
            }
12184
        }
12185
12186 10
        return $str_return . \implode('', $chars);
12187
    }
12188
12189
    /**
12190
     * Line-Wrap the string after $limit, but split the string by "$delimiter" before ...
12191
     *    ... so that we wrap the per line.
12192
     *
12193
     * @param string      $str             <p>The input string.</p>
12194
     * @param int         $width           [optional] <p>The column width.</p>
12195
     * @param string      $break           [optional] <p>The line is broken using the optional break parameter.</p>
12196
     * @param bool        $cut             [optional] <p>
12197
     *                                     If the cut is set to true, the string is
12198
     *                                     always wrapped at or before the specified width. So if you have
12199
     *                                     a word that is larger than the given width, it is broken apart.
12200
     *                                     </p>
12201
     * @param bool        $add_final_break [optional] <p>
12202
     *                                     If this flag is true, then the method will add a $break at the end
12203
     *                                     of the result string.
12204
     *                                     </p>
12205
     * @param string|null $delimiter       [optional] <p>
12206
     *                                     You can change the default behavior, where we split the string by newline.
12207
     *                                     </p>
12208
     *
12209
     * @return string
12210
     */
12211
    public static function wordwrap_per_line(
12212
        string $str,
12213
        int $width = 75,
12214
        string $break = "\n",
12215
        bool $cut = false,
12216
        bool $add_final_break = true,
12217
        string $delimiter = null
12218
    ): string {
12219 1
        if ($delimiter === null) {
12220 1
            $strings = \preg_split('/\\r\\n|\\r|\\n/', $str);
12221
        } else {
12222 1
            $strings = \explode($delimiter, $str);
12223
        }
12224
12225 1
        $string_helper_array = [];
12226 1
        if ($strings !== false) {
12227 1
            foreach ($strings as $value) {
12228 1
                $string_helper_array[] = self::wordwrap($value, $width, $break, $cut);
12229
            }
12230
        }
12231
12232 1
        if ($add_final_break) {
12233 1
            $final_break = $break;
12234
        } else {
12235 1
            $final_break = '';
12236
        }
12237
12238 1
        return \implode($delimiter ?? "\n", $string_helper_array) . $final_break;
12239
    }
12240
12241
    /**
12242
     * Returns an array of Unicode White Space characters.
12243
     *
12244
     * @return string[] an array with numeric code point as key and White Space Character as value
12245
     */
12246
    public static function ws(): array
12247
    {
12248 2
        return self::$WHITESPACE;
12249
    }
12250
12251
    /**
12252
     * Checks whether the passed string contains only byte sequences that are valid UTF-8 characters.
12253
     *
12254
     * @see http://hsivonen.iki.fi/php-utf8/
12255
     *
12256
     * @param string $str    <p>The string to be checked.</p>
12257
     * @param bool   $strict <p>Check also if the string is not UTF-16 or UTF-32.</p>
12258
     *
12259
     * @return bool
12260
     *
12261
     * @noinspection ReturnTypeCanBeDeclaredInspection
12262
     */
12263
    private static function is_utf8_string(string $str, bool $strict = false)
12264
    {
12265 108
        if ($str === '') {
12266 14
            return true;
12267
        }
12268
12269 102
        if ($strict === true) {
12270 2
            $is_binary = self::is_binary($str, true);
12271
12272 2
            if ($is_binary && self::is_utf16($str, false) !== false) {
12273 2
                return false;
12274
            }
12275
12276
            if ($is_binary && self::is_utf32($str, false) !== false) {
12277
                return false;
12278
            }
12279
        }
12280
12281 102
        if (self::pcre_utf8_support() !== true) {
12282
            // If even just the first character can be matched, when the /u
12283
            // modifier is used, then it's valid UTF-8. If the UTF-8 is somehow
12284
            // invalid, nothing at all will match, even if the string contains
12285
            // some valid sequences
12286
            return \preg_match('/^./us', $str, $ar) === 1;
12287
        }
12288
12289 102
        $mState = 0; // cached expected number of octets after the current octet
12290
        // until the beginning of the next UTF8 character sequence
12291 102
        $mUcs4 = 0; // cached Unicode character
12292 102
        $mBytes = 1; // cached expected number of octets in the current sequence
12293
12294 102
        if (self::$ORD === null) {
12295
            self::$ORD = self::getData('ord');
12296
        }
12297
12298 102
        $len = \strlen($str);
12299
        /** @noinspection ForeachInvariantsInspection */
12300 102
        for ($i = 0; $i < $len; ++$i) {
12301 102
            $in = self::$ORD[$str[$i]];
12302
12303 102
            if ($mState === 0) {
12304
                // When mState is zero we expect either a US-ASCII character or a
12305
                // multi-octet sequence.
12306 102
                if ((0x80 & $in) === 0) {
12307
                    // US-ASCII, pass straight through.
12308 97
                    $mBytes = 1;
12309 83
                } elseif ((0xE0 & $in) === 0xC0) {
12310
                    // First octet of 2 octet sequence.
12311 73
                    $mUcs4 = $in;
12312 73
                    $mUcs4 = ($mUcs4 & 0x1F) << 6;
12313 73
                    $mState = 1;
12314 73
                    $mBytes = 2;
12315 58
                } elseif ((0xF0 & $in) === 0xE0) {
12316
                    // First octet of 3 octet sequence.
12317 42
                    $mUcs4 = $in;
12318 42
                    $mUcs4 = ($mUcs4 & 0x0F) << 12;
12319 42
                    $mState = 2;
12320 42
                    $mBytes = 3;
12321 29
                } elseif ((0xF8 & $in) === 0xF0) {
12322
                    // First octet of 4 octet sequence.
12323 18
                    $mUcs4 = $in;
12324 18
                    $mUcs4 = ($mUcs4 & 0x07) << 18;
12325 18
                    $mState = 3;
12326 18
                    $mBytes = 4;
12327 13
                } elseif ((0xFC & $in) === 0xF8) {
12328
                    /* First octet of 5 octet sequence.
12329
                     *
12330
                     * This is illegal because the encoded codepoint must be either
12331
                     * (a) not the shortest form or
12332
                     * (b) outside the Unicode range of 0-0x10FFFF.
12333
                     * Rather than trying to resynchronize, we will carry on until the end
12334
                     * of the sequence and let the later error handling code catch it.
12335
                     */
12336 5
                    $mUcs4 = $in;
12337 5
                    $mUcs4 = ($mUcs4 & 0x03) << 24;
12338 5
                    $mState = 4;
12339 5
                    $mBytes = 5;
12340 10
                } elseif ((0xFE & $in) === 0xFC) {
12341
                    // First octet of 6 octet sequence, see comments for 5 octet sequence.
12342 5
                    $mUcs4 = $in;
12343 5
                    $mUcs4 = ($mUcs4 & 1) << 30;
12344 5
                    $mState = 5;
12345 5
                    $mBytes = 6;
12346
                } else {
12347
                    // Current octet is neither in the US-ASCII range nor a legal first
12348
                    // octet of a multi-octet sequence.
12349 102
                    return false;
12350
                }
12351 83
            } elseif ((0xC0 & $in) === 0x80) {
12352
12353
                // When mState is non-zero, we expect a continuation of the multi-octet
12354
                // sequence
12355
12356
                // Legal continuation.
12357 75
                $shift = ($mState - 1) * 6;
12358 75
                $tmp = $in;
12359 75
                $tmp = ($tmp & 0x0000003F) << $shift;
12360 75
                $mUcs4 |= $tmp;
12361
                // Prefix: End of the multi-octet sequence. mUcs4 now contains the final
12362
                // Unicode code point to be output.
12363 75
                if (--$mState === 0) {
12364
                    // Check for illegal sequences and code points.
12365
                    //
12366
                    // From Unicode 3.1, non-shortest form is illegal
12367
                    if (
12368 75
                        ($mBytes === 2 && $mUcs4 < 0x0080)
12369
                        ||
12370 75
                        ($mBytes === 3 && $mUcs4 < 0x0800)
12371
                        ||
12372 75
                        ($mBytes === 4 && $mUcs4 < 0x10000)
12373
                        ||
12374 75
                        ($mBytes > 4)
12375
                        ||
12376
                        // From Unicode 3.2, surrogate characters are illegal.
12377 75
                        (($mUcs4 & 0xFFFFF800) === 0xD800)
12378
                        ||
12379
                        // Code points outside the Unicode range are illegal.
12380 75
                        ($mUcs4 > 0x10FFFF)
12381
                    ) {
12382 8
                        return false;
12383
                    }
12384
                    // initialize UTF8 cache
12385 75
                    $mState = 0;
12386 75
                    $mUcs4 = 0;
12387 75
                    $mBytes = 1;
12388
                }
12389
            } else {
12390
                // ((0xC0 & (*in) != 0x80) && (mState != 0))
12391
                // Incomplete multi-octet sequence.
12392 35
                return false;
12393
            }
12394
        }
12395
12396 67
        return true;
12397
    }
12398
12399
    /**
12400
     * @param string $str
12401
     * @param bool   $use_lowercase      <p>Use uppercase by default, otherwise use lowercase.</p>
12402
     * @param bool   $use_full_case_fold <p>Convert not only common cases.</p>
12403
     *
12404
     * @return string
12405
     *
12406
     * @noinspection ReturnTypeCanBeDeclaredInspection
12407
     */
12408
    private static function fixStrCaseHelper(
12409
        string $str,
12410
        $use_lowercase = false,
12411
        $use_full_case_fold = false
12412
    ) {
12413 33
        $upper = self::$COMMON_CASE_FOLD['upper'];
12414 33
        $lower = self::$COMMON_CASE_FOLD['lower'];
12415
12416 33
        if ($use_lowercase === true) {
12417 2
            $str = \str_replace(
12418 2
                $upper,
12419 2
                $lower,
12420 2
                $str
12421
            );
12422
        } else {
12423 31
            $str = \str_replace(
12424 31
                $lower,
12425 31
                $upper,
12426 31
                $str
12427
            );
12428
        }
12429
12430 33
        if ($use_full_case_fold) {
12431 31
            static $FULL_CASE_FOLD = null;
12432 31
            if ($FULL_CASE_FOLD === null) {
12433 1
                $FULL_CASE_FOLD = self::getData('caseFolding_full');
12434
            }
12435
12436 31
            if ($use_lowercase === true) {
12437 2
                $str = \str_replace($FULL_CASE_FOLD[0], $FULL_CASE_FOLD[1], $str);
12438
            } else {
12439 29
                $str = \str_replace($FULL_CASE_FOLD[1], $FULL_CASE_FOLD[0], $str);
12440
            }
12441
        }
12442
12443 33
        return $str;
12444
    }
12445
12446
    /**
12447
     * get data from "/data/*.php"
12448
     *
12449
     * @param string $file
12450
     *
12451
     * @return array
12452
     *
12453
     * @noinspection ReturnTypeCanBeDeclaredInspection
12454
     */
12455
    private static function getData(string $file)
12456
    {
12457
        /** @noinspection PhpIncludeInspection */
12458
        /** @noinspection UsingInclusionReturnValueInspection */
12459
        /** @psalm-suppress UnresolvableInclude */
12460 6
        return include __DIR__ . '/data/' . $file . '.php';
12461
    }
12462
12463
    /**
12464
     * @return true|null
12465
     */
12466
    private static function initEmojiData()
12467
    {
12468 12
        if (self::$EMOJI_KEYS_CACHE === null) {
12469 1
            if (self::$EMOJI === null) {
12470 1
                self::$EMOJI = self::getData('emoji');
12471
            }
12472
12473 1
            \uksort(
12474 1
                self::$EMOJI,
12475
                static function (string $a, string $b): int {
12476 1
                    return \strlen($b) <=> \strlen($a);
12477 1
                }
12478
            );
12479
12480 1
            self::$EMOJI_KEYS_CACHE = \array_keys(self::$EMOJI);
12481 1
            self::$EMOJI_VALUES_CACHE = \array_values(self::$EMOJI);
12482
12483 1
            foreach (self::$EMOJI_KEYS_CACHE as $key) {
12484 1
                $tmp_key = \crc32($key);
12485 1
                self::$EMOJI_KEYS_REVERSIBLE_CACHE[] = '_-_PORTABLE_UTF8_-_' . $tmp_key . '_-_' . \strrev((string) $tmp_key) . '_-_8FTU_ELBATROP_-_';
12486
            }
12487
12488 1
            return true;
12489
        }
12490
12491 12
        return null;
12492
    }
12493
12494
    /**
12495
     * Checks whether mbstring "overloaded" is active on the server.
12496
     *
12497
     * @return bool
12498
     *
12499
     * @noinspection ReturnTypeCanBeDeclaredInspection
12500
     */
12501
    private static function mbstring_overloaded()
12502
    {
12503
        /**
12504
         * INI directive 'mbstring.func_overload' is deprecated since PHP 7.2
12505
         */
12506
12507
        /** @noinspection PhpComposerExtensionStubsInspection */
12508
        /** @noinspection PhpUsageOfSilenceOperatorInspection */
12509
        return \defined('MB_OVERLOAD_STRING')
12510
               &&
12511
               ((int) @\ini_get('mbstring.func_overload') & \MB_OVERLOAD_STRING);
12512
    }
12513
12514
    /**
12515
     * @param array    $strings
12516
     * @param bool     $remove_empty_values
12517
     * @param int|null $remove_short_values
12518
     *
12519
     * @return array
12520
     *
12521
     * @noinspection ReturnTypeCanBeDeclaredInspection
12522
     */
12523
    private static function reduce_string_array(
12524
        array $strings,
12525
        bool $remove_empty_values,
12526
        int $remove_short_values = null
12527
    ) {
12528
        // init
12529 2
        $return = [];
12530
12531 2
        foreach ($strings as &$str) {
12532
            if (
12533 2
                $remove_short_values !== null
12534
                &&
12535 2
                \mb_strlen($str) <= $remove_short_values
12536
            ) {
12537 2
                continue;
12538
            }
12539
12540
            if (
12541 2
                $remove_empty_values === true
12542
                &&
12543 2
                \trim($str) === ''
12544
            ) {
12545 2
                continue;
12546
            }
12547
12548 2
            $return[] = $str;
12549
        }
12550
12551 2
        return $return;
12552
    }
12553
12554
    /**
12555
     * rxClass
12556
     *
12557
     * @param string $s
12558
     * @param string $class
12559
     *
12560
     * @return string
12561
     *
12562
     * @noinspection ReturnTypeCanBeDeclaredInspection
12563
     */
12564
    private static function rxClass(string $s, string $class = '')
12565
    {
12566 33
        static $RX_CLASS_CACHE = [];
12567
12568 33
        $cache_key = $s . $class;
12569
12570 33
        if (isset($RX_CLASS_CACHE[$cache_key])) {
12571 21
            return $RX_CLASS_CACHE[$cache_key];
12572
        }
12573
12574 16
        $class_array = [$class];
12575
12576
        /** @noinspection SuspiciousLoopInspection */
12577
        /** @noinspection AlterInForeachInspection */
12578 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...
12579 15
            if ($s === '-') {
12580
                $class_array[0] = '-' . $class_array[0];
12581 15
            } elseif (!isset($s[2])) {
12582 15
                $class_array[0] .= \preg_quote($s, '/');
12583 1
            } elseif (self::strlen($s) === 1) {
12584 1
                $class_array[0] .= $s;
12585
            } else {
12586 15
                $class_array[] = $s;
12587
            }
12588
        }
12589
12590 16
        if ($class_array[0]) {
12591 16
            $class_array[0] = '[' . $class_array[0] . ']';
12592
        }
12593
12594 16
        if (\count($class_array) === 1) {
12595 16
            $return = $class_array[0];
12596
        } else {
12597
            $return = '(?:' . \implode('|', $class_array) . ')';
12598
        }
12599
12600 16
        $RX_CLASS_CACHE[$cache_key] = $return;
12601
12602 16
        return $return;
12603
    }
12604
12605
    /**
12606
     * Personal names such as "Marcus Aurelius" are sometimes typed incorrectly using lowercase ("marcus aurelius").
12607
     *
12608
     * @param string $names
12609
     * @param string $delimiter
12610
     * @param string $encoding
12611
     *
12612
     * @return string
12613
     *
12614
     * @noinspection ReturnTypeCanBeDeclaredInspection
12615
     */
12616
    private static function str_capitalize_name_helper(
12617
        string $names,
12618
        string $delimiter,
12619
        string $encoding = 'UTF-8'
12620
    ) {
12621
        // init
12622 1
        $name_helper_array = \explode($delimiter, $names);
12623 1
        if ($name_helper_array === false) {
12624
            return '';
12625
        }
12626
12627
        $special_cases = [
12628 1
            'names' => [
12629
                'ab',
12630
                'af',
12631
                'al',
12632
                'and',
12633
                'ap',
12634
                'bint',
12635
                'binte',
12636
                'da',
12637
                'de',
12638
                'del',
12639
                'den',
12640
                'der',
12641
                'di',
12642
                'dit',
12643
                'ibn',
12644
                'la',
12645
                'mac',
12646
                'nic',
12647
                'of',
12648
                'ter',
12649
                'the',
12650
                'und',
12651
                'van',
12652
                'von',
12653
                'y',
12654
                'zu',
12655
            ],
12656
            'prefixes' => [
12657
                'al-',
12658
                "d'",
12659
                'ff',
12660
                "l'",
12661
                'mac',
12662
                'mc',
12663
                'nic',
12664
            ],
12665
        ];
12666
12667 1
        foreach ($name_helper_array as &$name) {
12668 1
            if (\in_array($name, $special_cases['names'], true)) {
12669 1
                continue;
12670
            }
12671
12672 1
            $continue = false;
12673
12674 1
            if ($delimiter === '-') {
12675
                /** @noinspection AlterInForeachInspection */
12676 1
                foreach ((array) $special_cases['names'] as &$beginning) {
12677 1
                    if (self::strpos($name, $beginning, 0, $encoding) === 0) {
12678 1
                        $continue = true;
12679
                    }
12680
                }
12681
            }
12682
12683
            /** @noinspection AlterInForeachInspection */
12684 1
            foreach ((array) $special_cases['prefixes'] as &$beginning) {
12685 1
                if (self::strpos($name, $beginning, 0, $encoding) === 0) {
12686 1
                    $continue = true;
12687
                }
12688
            }
12689
12690 1
            if ($continue === true) {
12691 1
                continue;
12692
            }
12693
12694 1
            $name = self::ucfirst($name);
12695
        }
12696
12697 1
        return \implode($delimiter, $name_helper_array);
12698
    }
12699
12700
    /**
12701
     * Generic case-sensitive transformation for collation matching.
12702
     *
12703
     * @param string $str <p>The input string</p>
12704
     *
12705
     * @return string|null
12706
     */
12707
    private static function strtonatfold(string $str)
12708
    {
12709
        /** @noinspection PhpUndefinedClassInspection */
12710 6
        return \preg_replace(
12711 6
            '/\p{Mn}+/u',
12712 6
            '',
12713 6
            \Normalizer::normalize($str, \Normalizer::NFD)
12714
        );
12715
    }
12716
12717
    /**
12718
     * @param int|string $input
12719
     *
12720
     * @return string
12721
     *
12722
     * @noinspection ReturnTypeCanBeDeclaredInspection
12723
     */
12724
    private static function to_utf8_convert_helper($input)
12725
    {
12726
        // init
12727 31
        $buf = '';
12728
12729 31
        if (self::$ORD === null) {
12730 1
            self::$ORD = self::getData('ord');
12731
        }
12732
12733 31
        if (self::$CHR === null) {
12734 1
            self::$CHR = self::getData('chr');
12735
        }
12736
12737 31
        if (self::$WIN1252_TO_UTF8 === null) {
12738 1
            self::$WIN1252_TO_UTF8 = self::getData('win1252_to_utf8');
12739
        }
12740
12741 31
        $ordC1 = self::$ORD[$input];
12742 31
        if (isset(self::$WIN1252_TO_UTF8[$ordC1])) { // found in Windows-1252 special cases
12743 31
            $buf .= self::$WIN1252_TO_UTF8[$ordC1];
12744
        } else {
12745
            /** @noinspection OffsetOperationsInspection */
12746 1
            $cc1 = self::$CHR[$ordC1 / 64] | "\xC0";
12747 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...
12748 1
            $buf .= $cc1 . $cc2;
12749
        }
12750
12751 31
        return $buf;
12752
    }
12753
12754
    /**
12755
     * @param string $str
12756
     *
12757
     * @return string
12758
     *
12759
     * @noinspection ReturnTypeCanBeDeclaredInspection
12760
     */
12761
    private static function urldecode_unicode_helper(string $str)
12762
    {
12763 9
        $pattern = '/%u([0-9a-fA-F]{3,4})/';
12764 9
        if (\preg_match($pattern, $str)) {
12765 7
            $str = (string) \preg_replace($pattern, '&#x\\1;', $str);
12766
        }
12767
12768 9
        return $str;
12769
    }
12770
}
12771