Passed
Push — master ( a5a177...5e3b5e )
by Lars
04:10 queued 24s
created

UTF8   F

Complexity

Total Complexity 1689

Size/Duplication

Total Lines 12492
Duplicated Lines 0 %

Test Coverage

Coverage 78.93%

Importance

Changes 0
Metric Value
eloc 4328
dl 0
loc 12492
ccs 2839
cts 3597
cp 0.7893
rs 0.8
c 0
b 0
f 0
wmc 1689

293 Methods

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

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

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