Passed
Pull Request — master (#83)
by Lars
04:49
created

UTF8   F

Complexity

Total Complexity 1694

Size/Duplication

Total Lines 12540
Duplicated Lines 0 %

Test Coverage

Coverage 78.95%

Importance

Changes 0
Metric Value
wmc 1694
eloc 4356
dl 0
loc 12540
ccs 2854
cts 3615
cp 0.7895
rs 0.8
c 0
b 0
f 0

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

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

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