Passed
Push — master ( e44107...017c31 )
by Lars
03:56
created

UTF8   F

Complexity

Total Complexity 1694

Size/Duplication

Total Lines 12541
Duplicated Lines 0 %

Test Coverage

Coverage 79.75%

Importance

Changes 0
Metric Value
eloc 4356
dl 0
loc 12541
ccs 3036
cts 3807
cp 0.7975
rs 0.8
c 0
b 0
f 0
wmc 1694

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

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

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