PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.7.7
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.7.7
2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 1.1.0 All 77 releases
fluent-community / vendor / wpfluent / framework / src / WPFluent / Support / Str.php

Str.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.7.7, at vendor/wpfluent/framework/src/WPFluent/Support/Str.php

1,851 lines 58.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\Framework\Support;
4
5 use Exception;
6 use Traversable;
7 use JsonException;
8 use FluentCommunity\Framework\Foundation\App;
9 use FluentCommunity\Framework\Support\Helper;
10 use FluentCommunity\Framework\Support\Stringable;
11 use FluentCommunity\Framework\Support\MacroableTrait;
12
13 class Str
14 {
15 use MacroableTrait;
16
17 /**
18 * The cache of snake-cased words.
19 *
20 * @var array
21 */
22 protected static $snakeCache = [];
23
24 /**
25 * The cache of camel-cased words.
26 *
27 * @var array
28 */
29 protected static $camelCache = [];
30
31 /**
32 * The cache of studly-cased words.
33 *
34 * @var array
35 */
36 protected static $studlyCache = [];
37
38 /**
39 * Get a new stringable object from the given string.
40 *
41 * @param string $string
42 * @return \FluentCommunity\Framework\Support\Stringable
43 */
44 public static function of($string)
45 {
46 return new Stringable($string);
47 }
48
49 /**
50 * Makes an acronum from a string of words
51 *
52 * @param string $string
53 * @param string $delimiter
54 * @return string
55 */
56 public static function acronym($string, $delimiter = '')
57 {
58 if (empty($string)) {
59 return '';
60 }
61
62 $acronym = '';
63 foreach (preg_split('/[^\p{L}]+/u', $string) as $word) {
64 if(!empty($word)){
65 $first_letter = mb_substr($word, 0, 1);
66 $acronym .= $first_letter . $delimiter;
67 }
68 }
69
70 return $acronym;
71 }
72
73 /**
74 * Return the remainder of a string after the first occurrence of a given value.
75 *
76 * @param string $subject
77 * @param string $search
78 * @return string
79 */
80 public static function after($subject, $search)
81 {
82 return $search === '' ? $subject : array_reverse(explode($search, $subject, 2))[0];
83 }
84
85 /**
86 * Return the remainder of a string after the last occurrence of a given value.
87 *
88 * @param string $subject
89 * @param string $search
90 * @return string
91 */
92 public static function afterLast($subject, $search)
93 {
94 if ($search === '') {
95 return $subject;
96 }
97
98 $position = strrpos($subject, (string) $search);
99
100 if ($position === false) {
101 return $subject;
102 }
103
104 return substr($subject, $position + strlen($search));
105 }
106
107 /**
108 * Transliterate a UTF-8 value to ASCII.
109 *
110 * @param string $value
111 * @return string
112 * @see https://github.com/fzaninotto/Faker/blob/master/src/Faker/Provider/Internet.php#L245
113 */
114 public static function ascii($value)
115 {
116 static $arrayFrom, $arrayTo;
117
118 if (empty($arrayFrom)) {
119 $transliterationTable = [
120 'IJ' => 'I', 'Ö' => 'O', 'Œ' => 'O', 'Ü' => 'U', 'ä' => 'a', 'æ' => 'a',
121 'ij' => 'i', 'ö' => 'o', 'œ' => 'o', 'ü' => 'u', 'ß' => 's', 'ſ' => 's',
122 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', '�
123 ' => 'A',
124 'Æ' => 'A', 'Ā' => 'A', 'Ą' => 'A', 'Ă' => 'A', 'Ç' => 'C', 'Ć' => 'C',
125 'Č' => 'C', 'Ĉ' => 'C', 'Ċ' => 'C', 'Ď' => 'D', 'Đ' => 'D', 'È' => 'E',
126 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ē' => 'E', 'Ę' => 'E', 'Ě' => 'E',
127 'Ĕ' => 'E', 'Ė' => 'E', 'Ĝ' => 'G', 'Ğ' => 'G', 'Ġ' => 'G', 'Ģ' => 'G',
128 'Ĥ' => 'H', 'Ħ' => 'H', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I',
129 'Ī' => 'I', 'Ĩ' => 'I', 'Ĭ' => 'I', 'Į' => 'I', 'İ' => 'I', 'Ĵ' => 'J',
130 'Ķ' => 'K', 'Ľ' => 'K', 'Ĺ' => 'K', 'Ļ' => 'K', 'Ŀ' => 'K', 'Ł' => 'L',
131 'Ñ' => 'N', 'Ń' => 'N', 'Ň' => 'N', '�
132 ' => 'N', 'Ŋ' => 'N', 'Ò' => 'O',
133 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ø' => 'O', 'Ō' => 'O', 'Ő' => 'O',
134 'Ŏ' => 'O', 'Ŕ' => 'R', 'Ř' => 'R', 'Ŗ' => 'R', 'Ś' => 'S', 'Ş' => 'S',
135 'Ŝ' => 'S', 'Ș' => 'S', 'Š' => 'S', 'Ť' => 'T', 'Ţ' => 'T', 'Ŧ' => 'T',
136 'Ț' => 'T', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ū' => 'U', 'Ů' => 'U',
137 'Ű' => 'U', 'Ŭ' => 'U', 'Ũ' => 'U', 'Ų' => 'U', 'Ŵ' => 'W', 'Ŷ' => 'Y',
138 'Ÿ' => 'Y', 'Ý' => 'Y', 'Ź' => 'Z', 'Ż' => 'Z', 'Ž' => 'Z', 'à' => 'a',
139 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ā' => 'a', '�
140 ' => 'a', 'ă' => 'a',
141 'å' => 'a', 'ç' => 'c', 'ć' => 'c', 'č' => 'c', 'ĉ' => 'c', 'ċ' => 'c',
142 'ď' => 'd', 'đ' => 'd', 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e',
143 'ē' => 'e', 'ę' => 'e', 'ě' => 'e', 'ĕ' => 'e', 'ė' => 'e', 'ƒ' => 'f',
144 'ĝ' => 'g', 'ğ' => 'g', 'ġ' => 'g', 'ģ' => 'g', 'ĥ' => 'h', 'ħ' => 'h',
145 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ī' => 'i', 'ĩ' => 'i',
146 'ĭ' => 'i', 'į' => 'i', 'ı' => 'i', 'ĵ' => 'j', 'ķ' => 'k', 'ĸ' => 'k',
147 'ł' => 'l', 'ľ' => 'l', 'ĺ' => 'l', 'ļ' => 'l', 'ŀ' => 'l', 'ñ' => 'n',
148 'ń' => 'n', 'ň' => 'n', 'ņ' => 'n', 'ʼn' => 'n', 'ŋ' => 'n', 'ò' => 'o',
149 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ø' => 'o', 'ō' => 'o', 'ő' => 'o',
150 'ŏ' => 'o', 'ŕ' => 'r', 'ř' => 'r', 'ŗ' => 'r', 'ś' => 's', 'š' => 's',
151 'ť' => 't', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ū' => 'u', 'ů' => 'u',
152 'ű' => 'u', 'ŭ' => 'u', 'ũ' => 'u', 'ų' => 'u', 'ŵ' => 'w', 'ÿ' => 'y',
153 'ý' => 'y', 'ŷ' => 'y', 'ż' => 'z', 'ź' => 'z', 'ž' => 'z', 'Α' => 'A',
154 'Ά' => 'A', '' => 'A', '' => 'A', '' => 'A', '' => 'A', '' => 'A',
155 '' => 'A', '' => 'A', '' => 'A', '' => 'A', '' => 'A', '' => 'A',
156 '' => 'A', '' => 'A', '' => 'A', '' => 'A', '' => 'A', '' => 'A',
157 '' => 'A', '' => 'A', '' => 'A', 'Β' => 'B', 'Γ' => 'G', 'Δ' => 'D',
158 'Ε' => 'E', 'Έ' => 'E', '' => 'E', '' => 'E', '' => 'E', '' => 'E',
159 '' => 'E', '' => 'E', '' => 'E', 'Ζ' => 'Z', 'Η' => 'I', 'Ή' => 'I',
160 '' => 'I', '' => 'I', '' => 'I', '' => 'I', '' => 'I', '' => 'I',
161 '' => 'I', '' => 'I', '' => 'I', '' => 'I', '' => 'I', '' => 'I',
162 '' => 'I', '' => 'I', '' => 'I', '' => 'I', '' => 'I', '' => 'I',
163 'Θ' => 'T', 'Ι' => 'I', 'Ί' => 'I', 'Ϊ' => 'I', '' => 'I', '' => 'I',
164 '' => 'I', '' => 'I', '' => 'I', '' => 'I', '' => 'I', 'Ἷ' => 'I',
165 '' => 'I', '' => 'I', '' => 'I', 'Κ' => 'K', 'Λ' => 'L', 'Μ' => 'M',
166 'Ν' => 'N', 'Ξ' => 'K', 'Ο' => 'O', 'Ό' => 'O', '' => 'O', '' => 'O',
167 '' => 'O', '' => 'O', '' => 'O', '' => 'O', '' => 'O', 'Π' => 'P',
168 'Ρ' => 'R', '' => 'R', 'Σ' => 'S', 'Τ' => 'T', 'Υ' => 'Y', 'Ύ' => 'Y',
169 'Ϋ' => 'Y', '' => 'Y', '' => 'Y', '' => 'Y', '' => 'Y', '' => 'Y',
170 '' => 'Y', '' => 'Y', 'Φ' => 'F', 'Χ' => 'X', 'Ψ' => 'P', 'Ω' => 'O',
171 'Ώ' => 'O', '' => 'O', '' => 'O', '' => 'O', '' => 'O', '' => 'O',
172 '' => 'O', '' => 'O', '' => 'O', '' => 'O', '' => 'O', '' => 'O',
173 '' => 'O', '' => 'O', '' => 'O', '' => 'O', '' => 'O', '' => 'O',
174 '' => 'O', 'α' => 'a', 'ά' => 'a', '' => 'a', '' => 'a', '' => 'a',
175 'ἃ' => 'a', 'ἄ' => 'a', '�
176 ' => 'a', '' => 'a', '' => 'a', '' => 'a',
177 'ᾁ' => 'a', 'ᾂ' => 'a', 'ᾃ' => 'a', 'ᾄ' => 'a', '�
178 ' => 'a', '' => 'a',
179 '' => 'a', '' => 'a', '' => 'a', '' => 'a', '' => 'a', '' => 'a',
180 '' => 'a', '' => 'a', '' => 'a', 'β' => 'b', 'γ' => 'g', 'δ' => 'd',
181 'ε' => 'e', 'έ' => 'e', '' => 'e', '' => 'e', '' => 'e', '' => 'e',
182 '' => 'e', '' => 'e', '' => 'e', 'ζ' => 'z', 'η' => 'i', 'ή' => 'i',
183 '' => 'i', '' => 'i', '' => 'i', '' => 'i', '' => 'i', '' => 'i',
184 '' => 'i', '' => 'i', '' => 'i', '' => 'i', '' => 'i', '' => 'i',
185 '' => 'i', '' => 'i', '' => 'i', '' => 'i', '' => 'i', '' => 'i',
186 '' => 'i', '' => 'i', '' => 'i', '' => 'i', 'θ' => 't', 'ι' => 'i',
187 'ί' => 'i', 'ϊ' => 'i', 'ΐ' => 'i', '' => 'i', '' => 'i', '' => 'i',
188 '' => 'i', '' => 'i', '' => 'i', '' => 'i', '' => 'i', '' => 'i',
189 '' => 'i', '' => 'i', '' => 'i', '' => 'i', '' => 'i', 'κ' => 'k',
190 'λ' => 'l', 'μ' => 'm', 'ν' => 'n', 'ξ' => 'k', 'ο' => 'o', 'ό' => 'o',
191 'ὀ' => 'o', 'ὁ' => 'o', 'ὂ' => 'o', 'ὃ' => 'o', 'ὄ' => 'o', '�
192 ' => 'o',
193 '' => 'o', 'π' => 'p', 'ρ' => 'r', '' => 'r', '' => 'r', 'σ' => 's',
194 'ς' => 's', 'τ' => 't', '�
195 ' => 'y', 'ύ' => 'y', 'ϋ' => 'y', 'ΰ' => 'y',
196 '' => 'y', '' => 'y', '' => 'y', '' => 'y', '' => 'y', '' => 'y',
197 '' => 'y', '' => 'y', '' => 'y', '' => 'y', '' => 'y', '' => 'y',
198 '' => 'y', '' => 'y', 'φ' => 'f', 'χ' => 'x', 'ψ' => 'p', 'ω' => 'o',
199 'ώ' => 'o', '' => 'o', '' => 'o', '' => 'o', '' => 'o', '' => 'o',
200 '' => 'o', '' => 'o', '' => 'o', '' => 'o', '' => 'o', '' => 'o',
201 '' => 'o', '' => 'o', '' => 'o', '' => 'o', '' => 'o', '' => 'o',
202 '' => 'o', '' => 'o', '' => 'o', '' => 'o', '' => 'o', 'А' => 'A',
203 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D', 'Е' => 'E', 'Ё' => 'E',
204 'Ж' => 'Z', 'З' => 'Z', 'И' => 'I', 'Й' => 'I', 'К' => 'K', 'Л' => 'L',
205 'М' => 'M', 'Н' => 'N', 'О' => 'O', 'П' => 'P', 'Р' => 'R', 'С' => 'S',
206 'Т' => 'T', 'У' => 'U', 'Ф' => 'F', 'Х' => 'K', 'Ц' => 'T', 'Ч' => 'C',
207 'Ш' => 'S', 'Щ' => 'S', 'Ы' => 'Y', 'Э' => 'E', 'Ю' => 'Y', 'Я' => 'Y',
208 'а' => 'A', 'б' => 'B', 'в' => 'V', 'г' => 'G', 'д' => 'D', 'е' => 'E',
209 'ё' => 'E', 'ж' => 'Z', 'з' => 'Z', 'и' => 'I', 'й' => 'I', 'к' => 'K',
210 'л' => 'L', 'м' => 'M', 'н' => 'N', 'о' => 'O', 'п' => 'P', 'р' => 'R',
211 'с' => 'S', 'т' => 'T', 'у' => 'U', 'ф' => 'F', '�
212 ' => 'K', 'ц' => 'T',
213 'ч' => 'C', 'ш' => 'S', 'щ' => 'S', 'ы' => 'Y', 'э' => 'E', 'ю' => 'Y',
214 'я' => 'Y', 'ð' => 'd', 'Ð' => 'D', 'þ' => 't', 'Þ' => 'T', '' => 'a',
215 '' => 'b', '' => 'g', '' => 'd', '' => 'e', '' => 'v', '' => 'z',
216 '' => 't', '' => 'i', '' => 'k', '' => 'l', '' => 'm', '' => 'n',
217 '' => 'o', '' => 'p', '' => 'z', '' => 'r', '' => 's', '' => 't',
218 '' => 'u', '' => 'p', '' => 'k', '' => 'g', '' => 'q', '' => 's',
219 '' => 'c', '' => 't', '' => 'd', '' => 't', '' => 'c', '' => 'k',
220 '' => 'j', '' => 'h', 'ţ' => 't', 'ʼ' => "'", '̧' => '', '' => 'h',
221 '' => "'", '' => "'", '' => 'u', '/' => '', 'ế' => 'e', '' => 'a',
222 '' => 'i', '' => 'a', '' => 'e', '' => 'i', '' => 'o', '' => 'e',
223 'ơ' => 'o', '' => 'a', '' => 'a', 'ư' => 'u', '' => 'a', '' => 'a',
224 '' => 'd', '' => 'H', '' => 'D', 'ș' => 's', 'ț' => 't', '' => 'o',
225 '' => 'a', 'ş' => 's', "'" => '', 'ու' => 'u', 'ա' => 'a', 'բ' => 'b',
226 'գ' => 'g', 'դ' => 'd', 'ե' => 'e', 'զ' => 'z', 'է' => 'e', 'ը' => 'y',
227 'թ' => 't', 'ժ' => 'zh', 'ի' => 'i', 'լ' => 'l', 'խ' => 'kh', 'ծ' => 'ts',
228 'կ' => 'k', 'հ' => 'h', 'ձ' => 'dz', 'ղ' => 'gh', 'ճ' => 'ch', 'մ' => 'm',
229 'յ' => 'y', 'ն' => 'n', 'շ' => 'sh', 'ո' => 'o', 'չ' => 'ch', 'պ' => 'p',
230 'ջ' => 'j', 'ռ' => 'r', 'ս' => 's', 'վ' => 'v', 'տ' => 't', 'ր' => 'r',
231 'ց' => 'ts', 'փ' => 'p', 'ք' => 'q', 'և' => 'ev', '�
232 ' => 'o', 'ֆ' => 'f',
233 ];
234 $arrayFrom = array_keys($transliterationTable);
235 $arrayTo = array_values($transliterationTable);
236 }
237
238 return str_replace($arrayFrom, $arrayTo, $value);
239 }
240
241 /**
242 * Transliterate a string to its closest ASCII representation.
243 *
244 * @param string $string
245 * @return string
246 * @see https://github.com/fzaninotto/Faker/blob/master/src/Faker/Provider/Internet.php#L229
247 */
248 public static function transliterate($string)
249 {
250 if (0 === preg_match('/[^A-Za-z0-9_.]/', $string)) {
251 return $string;
252 }
253
254 $transString = static::ascii($string);
255
256 return preg_replace('/[^A-Za-z0-9_.]/u', '', $transString);
257 }
258
259 /**
260 * Get the portion of a string before the first occurrence of a given value.
261 *
262 * @param string $subject
263 * @param string $search
264 * @return string
265 */
266 public static function before($subject, $search)
267 {
268 if ($search === '') {
269 return $subject;
270 }
271
272 $result = strstr($subject, (string) $search, true);
273
274 return $result === false ? $subject : $result;
275 }
276
277 /**
278 * Get the portion of a string before the last occurrence of a given value.
279 *
280 * @param string $subject
281 * @param string $search
282 * @return string
283 */
284 public static function beforeLast($subject, $search)
285 {
286 if ($search === '') {
287 return $subject;
288 }
289
290 $pos = mb_strrpos($subject, $search);
291
292 if ($pos === false) {
293 return $subject;
294 }
295
296 return static::substr($subject, 0, $pos);
297 }
298
299 /**
300 * Get the portion of a string between two given values.
301 *
302 * @param string $subject
303 * @param string $from
304 * @param string $to
305 * @return string
306 */
307 public static function between($subject, $from, $to)
308 {
309 if ($from === '' || $to === '') {
310 return $subject;
311 }
312
313 return static::beforeLast(static::after($subject, $from), $to);
314 }
315
316 /**
317 * Get the smallest possible portion of a string between two given values.
318 *
319 * @param string $subject
320 * @param string $from
321 * @param string $to
322 * @return string
323 */
324 public static function betweenFirst($subject, $from, $to)
325 {
326 if ($from === '' || $to === '') {
327 return $subject;
328 }
329
330 return static::before(static::after($subject, $from), $to);
331 }
332
333 /**
334 * Convert a value to camel case.
335 *
336 * @param string $value
337 * @return string
338 */
339 public static function camel($value)
340 {
341 if (isset(static::$camelCache[$value])) {
342 return static::$camelCache[$value];
343 }
344
345 return static::$camelCache[$value] = lcfirst(static::studly($value));
346 }
347
348 /**
349 * Get the character at the specified index.
350 *
351 * @param string $subject
352 * @param int $index
353 * @return string|false
354 */
355 public static function charAt($subject, $index)
356 {
357 $length = mb_strlen($subject);
358
359 if ($index < 0 ? $index < -$length : $index > $length - 1) {
360 return false;
361 }
362
363 return mb_substr($subject, $index, 1);
364 }
365
366 /**
367 * Remove the given string(s) if it exists at the start of the haystack.
368 *
369 * @param string $subject
370 * @param string|array $needle
371 * @return string
372 */
373 public static function chopStart($subject, $needle)
374 {
375 foreach ((array) $needle as $n) {
376 if (str_starts_with($subject, $n)) {
377 return substr($subject, strlen($n));
378 }
379 }
380
381 return $subject;
382 }
383
384 /**
385 * Remove the given string(s) if it exists at the end of the haystack.
386 *
387 * @param string $subject
388 * @param string|array $needle
389 * @return string
390 */
391 public static function chopEnd($subject, $needle)
392 {
393 foreach ((array) $needle as $n) {
394 if (str_ends_with($subject, $n)) {
395 return substr($subject, 0, -strlen($n));
396 }
397 }
398
399 return $subject;
400 }
401
402 /**
403 * Determine if a given string contains a given substring.
404 *
405 * @param string $haystack
406 * @param string|iterable<string> $needles
407 * @param bool $ignoreCase
408 * @return bool
409 */
410 public static function contains($haystack, $needles, $ignoreCase = false)
411 {
412 if ($ignoreCase) {
413 $haystack = mb_strtolower($haystack);
414 }
415
416 if (! is_iterable($needles)) {
417 $needles = (array) $needles;
418 }
419
420 foreach ($needles as $needle) {
421 if ($ignoreCase) {
422 $needle = mb_strtolower($needle);
423 }
424
425 if ($needle !== '' && str_contains($haystack, $needle)) {
426 return true;
427 }
428 }
429
430 return false;
431 }
432
433 /**
434 * Determine if a given string contains all array values.
435 *
436 * @param string $haystack
437 * @param iterable<string> $needles
438 * @param bool $ignoreCase
439 * @return bool
440 */
441 public static function containsAll($haystack, $needles, $ignoreCase = false)
442 {
443 foreach ($needles as $needle) {
444 if (! static::contains($haystack, $needle, $ignoreCase)) {
445 return false;
446 }
447 }
448
449 return true;
450 }
451
452 /**
453 * Replace consecutive instances of a given character
454 * with a single character in the given string.
455 *
456 * @param string $string
457 * @param string $character
458 * @return string
459 */
460 public static function deduplicate(string $string, string $character = ' ')
461 {
462 return preg_replace(
463 '/'.preg_quote($character, '/').'+/u', $character, $string
464 );
465 }
466
467 /**
468 * Determine if a given string ends with a given substring.
469 *
470 * @param string $haystack
471 * @param array|string $needles
472 * @return bool
473 */
474 public static function endsWith($haystack, $needles)
475 {
476 if (! is_iterable($needles)) {
477 $needles = (array) $needles;
478 }
479
480 if (is_null($haystack)) {
481 return false;
482 }
483
484 foreach ($needles as $needle) {
485 if ((string) $needle !== '' && str_ends_with($haystack, $needle)) {
486 return true;
487 }
488 }
489
490 return false;
491 }
492
493 /**
494 * Cap a string with a single instance of a given value.
495 *
496 * @param string $value
497 * @param string $cap
498 * @return string
499 */
500 public static function finish($value, $cap)
501 {
502 $quoted = preg_quote($cap, '/');
503
504 return preg_replace('/(?:'.$quoted.')+$/u', '', $value).$cap;
505 }
506
507 /**
508 * Determine if a given string matches a given pattern.
509 *
510 * @param string|array $pattern
511 * @param string $value
512 * @return bool
513 */
514 public static function is($pattern, $value)
515 {
516 $patterns = Arr::wrap($pattern);
517
518 $value = (string) $value;
519
520 if (empty($patterns)) {
521 return false;
522 }
523
524 foreach ($patterns as $pattern) {
525 $pattern = (string) $pattern;
526
527 // If the given value is an exact match we can of course return
528 // true right from the beginning. Otherwise, we will translate
529 // asterisks and do an actual pattern match against the
530 // two strings to see if they match.
531 if ($pattern == $value) {
532 return true;
533 }
534
535 $pattern = preg_quote($pattern, '#');
536
537 // Asterisks are translated into zero-or-more regular expression
538 // wildcards to make it convenient to check if the strings
539 // starts with the given pattern such as "library/*",
540 // making any string check convenient.
541 $pattern = str_replace('\*', '.*', $pattern);
542
543 if (preg_match('#^'.$pattern.'\z#u', $value) === 1) {
544 return true;
545 }
546 }
547
548 return false;
549 }
550
551 /**
552 * Converts a non-boolean value to boolean
553 * @param string|int $str
554 * @return bool|null
555 */
556 public static function toBool($str)
557 {
558 if (is_bool($str)) {
559 return $str;
560 }
561
562 $truthy = ['yes', 'on', 'true', '1', 1];
563
564 $falsy = ['no', 'off', 'false', '0', 0, ''];
565
566 if (in_array($str, $truthy, true)) {
567 return true;
568 } elseif (in_array($str, $falsy, true)) {
569 return false;
570 }
571 }
572
573 /**
574 * Determine if a given string is 7 bit ASCII.
575 *
576 * @param string $value
577 * @return bool
578 * @see https://developer.wordpress.org/reference/classes/requests_idnaencoder/is_ascii/
579 */
580 public static function isAscii($value)
581 {
582 return (preg_match('/(?:[^\x00-\x7F])/', $value) !== 1);
583 }
584
585 /**
586 * Checks if the word(s) are in capitalized form.
587 * @param string $str
588 * @param boolean $onlyFirst if true, checks only the first charatcer
589 * @return boolean
590 */
591 public static function isCapitalized($str, $onlyFirst = false)
592 {
593 if ($onlyFirst) {
594 return static::isUpper($str[0]);
595 }
596
597 if ($words = preg_split('/\s/u', $str)) {
598 foreach ($words as $word) {
599 $isCap[] = static::isUpper($word[0]) && static::isLower(mb_substr($word, 1));
600 }
601 return count(array_filter($isCap)) === count($isCap);
602 }
603 }
604
605 /**
606 * Checks if the first character is in capital form.
607 *
608 * @param string $str
609 * @return boolean
610 */
611 public static function isCapital($str)
612 {
613 return static::isCapitalized($str, true);
614 }
615
616 /**
617 * Checks if the chracters are in upper case
618 *
619 * @param string $str
620 * @return boolean
621 */
622 public static function isUpper($str)
623 {
624 return $str === static::upper($str);
625 }
626
627 /**
628 * Checks if the chracters are in lower case
629 *
630 * @param string $str
631 * @return boolean
632 */
633 public static function isLower($str)
634 {
635 return $str === static::lower($str);
636 }
637
638 /**
639 * Checks if two words sounds alike
640 *
641 * @param string $str1
642 * @param string $str2
643 * @return bool
644 */
645 public static function soundsAlike($str1, $str2)
646 {
647 return soundex($str1) == soundex($str2);
648 }
649
650 /**
651 * Checks if two words are similar
652 *
653 * @param string $str1
654 * @param string $str2
655 *
656 * @return bool
657 */
658 public static function isSimilar($str1, $str2, $accuracy = 60)
659 {
660 $percent = static::similarityOf($str1, $str2);
661
662 return $percent > $accuracy;
663 }
664
665 /**
666 * Checks if two words are similar
667 *
668 * @param string $str1
669 * @param string $str2
670 *
671 * @return bool
672 */
673 public static function similarityOf($str1, $str2)
674 {
675 similar_text($str1, $str2, $percent);
676
677 return $percent;
678 }
679
680 /**
681 * Determine if a given value is valid JSON.
682 *
683 * @param mixed $value
684 * @return bool
685 */
686 public static function isJson($value)
687 {
688 if (! is_string($value)) {
689 return false;
690 }
691
692 try {
693 json_decode($value, true, 512, JSON_THROW_ON_ERROR);
694 } catch (JsonException $e) {
695 return false;
696 }
697
698 return true;
699 }
700
701 /**
702 * Determine if a given value is a valid URL.
703 *
704 * @param mixed $value
705 * @param array $protocols
706 * @return bool
707 */
708 public static function isUrl($value, array $protocols = [])
709 {
710 if (! is_string($value)) {
711 return false;
712 }
713
714 $protocolList = empty($protocols)
715 ? 'aaa|aaas|about|acap|acct|acd|acr|adiumxtra|adt|afp|afs|aim|amss|android|appdata|apt|ark|attachment|aw|barion|beshare|bitcoin|bitcoincash|blob|bolo|browserext|calculator|callto|cap|cast|casts|chrome|chrome-extension|cid|coap|coap\+tcp|coap\+ws|coaps|coaps\+tcp|coaps\+ws|com-eventbrite-attendee|content|conti|crid|cvs|dab|data|dav|diaspora|dict|did|dis|dlna-playcontainer|dlna-playsingle|dns|dntp|dpp|drm|drop|dtn|dvb|ed2k|elsi|example|facetime|fax|feed|feedready|file|filesystem|finger|first-run-pen-experience|fish|fm|ftp|fuchsia-pkg|geo|gg|git|gizmoproject|go|gopher|graph|gtalk|h323|ham|hcap|hcp|http|https|hxxp|hxxps|hydrazone|iax|icap|icon|im|imap|info|iotdisco|ipn|ipp|ipps|irc|irc6|ircs|iris|iris\.beep|iris\.lwz|iris\.xpc|iris\.xpcs|isostore|itms|jabber|jar|jms|keyparc|lastfm|ldap|ldaps|leaptofrogans|lorawan|lvlt|magnet|mailserver|mailto|maps|market|message|mid|mms|modem|mongodb|moz|ms-access|ms-browser-extension|ms-calculator|ms-drive-to|ms-enrollment|ms-excel|ms-eyecontrolspeech|ms-gamebarservices|ms-gamingoverlay|ms-getoffice|ms-help|ms-infopath|ms-inputapp|ms-lockscreencomponent-config|ms-media-stream-id|ms-mixedrealitycapture|ms-mobileplans|ms-officeapp|ms-people|ms-project|ms-powerpoint|ms-publisher|ms-restoretabcompanion|ms-screenclip|ms-screensketch|ms-search|ms-search-repair|ms-secondary-screen-controller|ms-secondary-screen-setup|ms-settings|ms-settings-airplanemode|ms-settings-bluetooth|ms-settings-camera|ms-settings-cellular|ms-settings-cloudstorage|ms-settings-connectabledevices|ms-settings-displays-topology|ms-settings-emailandaccounts|ms-settings-language|ms-settings-location|ms-settings-lock|ms-settings-nfctransactions|ms-settings-notifications|ms-settings-power|ms-settings-privacy|ms-settings-proximity|ms-settings-screenrotation|ms-settings-wifi|ms-settings-workplace|ms-spd|ms-sttoverlay|ms-transit-to|ms-useractivityset|ms-virtualtouchpad|ms-visio|ms-walk-to|ms-whiteboard|ms-whiteboard-cmd|ms-word|msnim|msrp|msrps|mss|mtqp|mumble|mupdate|mvn|news|nfs|ni|nih|nntp|notes|ocf|oid|onenote|onenote-cmd|opaquelocktoken|openpgp4fpr|pack|palm|paparazzi|payto|pkcs11|platform|pop|pres|prospero|proxy|pwid|psyc|pttp|qb|query|redis|rediss|reload|res|resource|rmi|rsync|rtmfp|rtmp|rtsp|rtsps|rtspu|s3|secondlife|service|session|sftp|sgn|shttp|sieve|simpleledger|sip|sips|skype|smb|sms|smtp|snews|snmp|soap\.beep|soap\.beeps|soldat|spiffe|spotify|ssh|steam|stun|stuns|submit|svn|tag|teamspeak|tel|teliaeid|telnet|tftp|tg|things|thismessage|tip|tn3270|tool|ts3server|turn|turns|tv|udp|unreal|urn|ut2004|v-event|vemmi|ventrilo|videotex|vnc|view-source|wais|webcal|wpid|ws|wss|wtai|wyciwyg|xcon|xcon-userid|xfire|xmlrpc\.beep|xmlrpc\.beeps|xmpp|xri|ymsgr|z39\.50|z39\.50r|z39\.50s'
716 : implode('|', $protocols);
717
718 /*
719 * This pattern is derived from Symfony\Component\Validator\Constraints\UrlValidator (5.0.7).
720 *
721 * (c) Fabien Potencier <fabien@symfony.com> http://symfony.com
722 */
723 $pattern = '~^
724 (FLUENT_PROTOCOLS):// # protocol
725 (((?:[\_\.\pL\pN-]|%[0-9A-Fa-f]{2})+:)?((?:[\_\.\pL\pN-]|%[0-9A-Fa-f]{2})+)@)? # basic auth
726 (
727 ([\pL\pN\pS\-\_\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name
728 | # or
729 \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address
730 | # or
731 \[
732 (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::))))
733 \] # an IPv6 address
734 )
735 (:[0-9]+)? # a port (optional)
736 (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})* )* # a path
737 (?:\? (?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%[0-9A-Fa-f]{2})* )? # a query (optional)
738 (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})* )? # a fragment (optional)
739 $~ixu';
740
741 return preg_match(str_replace('FLUENT_PROTOCOLS', $protocolList, $pattern), $value) > 0;
742 }
743
744 /**
745 * Determine if a given string is a valid UUID.
746 *
747 * @param string $value
748 * @return bool
749 */
750 public static function isUuid($value)
751 {
752 return wp_is_uuid($value);
753 }
754
755 /**
756 * Convert a string to kebab case.
757 *
758 * @param string $value
759 * @return string
760 */
761 public static function kebab($value)
762 {
763 return static::snake($value, '-');
764 }
765
766 /**
767 * Return the length of the given string.
768 *
769 * @param string $value
770 * @param string|null $encoding
771 * @return int
772 */
773 public static function length($value, $encoding = null)
774 {
775 if ($encoding) {
776 return mb_strlen($value, $encoding);
777 }
778
779 return mb_strlen($value);
780 }
781
782 /**
783 * Limit the number of characters in a string.
784 *
785 * @param string $value
786 * @param int $limit
787 * @param string $end
788 * @param bool $preserveWords
789 * @return string
790 */
791 public static function limit(
792 $value, $limit = 100, $end = '...', $preserveWords = false
793 )
794 {
795 if (mb_strlen($value, 'UTF-8') <= $limit) {
796 return $value;
797 }
798
799 if (! $preserveWords) {
800 return rtrim(mb_substr($value, 0, $limit, 'UTF-8')).$end;
801 }
802
803 $value = trim(preg_replace('/[\n\r]+/', ' ', strip_tags($value)));
804
805 $trimmed = rtrim(mb_substr($value, 0, $limit, 'UTF-8'));
806
807 if (mb_substr($value, $limit, 1, 'UTF-8') === ' ') {
808 return $trimmed.$end;
809 }
810
811 return preg_replace("/(.*)\s.*/", '$1', $trimmed).$end;
812 }
813
814 /**
815 * Convert the given string to lower-case.
816 *
817 * @param string $value
818 * @return string
819 */
820 public static function lower($value)
821 {
822 return mb_strtolower($value, 'UTF-8');
823 }
824
825 /**
826 * Limit the number of words in a string.
827 *
828 * @param string $value
829 * @param int $words
830 * @param string $end
831 * @return string
832 */
833 public static function words($value, $words = 100, $end = '...')
834 {
835 preg_match('/^\s*+(?:\S++\s*+){1,'.$words.'}/u', $value, $matches);
836
837 if (! isset($matches[0]) || static::length($value) === static::length($matches[0])) {
838 return $value;
839 }
840
841 return rtrim($matches[0]).$end;
842 }
843
844 /**
845 * Masks a portion of a string with a repeated character.
846 *
847 * @param string $string
848 * @param string $character
849 * @param int $index
850 * @param int|null $length
851 * @param string $encoding
852 * @return string
853 */
854 public static function mask(
855 $string, $character, $index, $length = null, $encoding = 'UTF-8'
856 )
857 {
858 if ($character === '') {
859 return $string;
860 }
861
862 if (is_null($length) && PHP_MAJOR_VERSION < 8) {
863 $length = mb_strlen($string, $encoding);
864 }
865
866 $segment = mb_substr($string, $index, $length, $encoding);
867
868 if ($segment === '') {
869 return $string;
870 }
871
872 $strlen = mb_strlen($string, $encoding);
873 $startIndex = $index;
874
875 if ($index < 0) {
876 $startIndex = $index < -$strlen ? 0 : $strlen + $index;
877 }
878
879 $start = mb_substr($string, 0, $startIndex, $encoding);
880 $segmentLen = mb_strlen($segment, $encoding);
881 $end = mb_substr($string, $startIndex + $segmentLen);
882
883 return $start.str_repeat(mb_substr($character, 0, 1, $encoding), $segmentLen).$end;
884 }
885
886 /**
887 * Get the string matching the given pattern.
888 *
889 * @param string $pattern
890 * @param string $subject
891 * @return string
892 */
893 public static function match($pattern, $subject)
894 {
895 preg_match($pattern, $subject, $matches);
896
897 if (! $matches) {
898 return '';
899 }
900
901 return $matches[1] ?? $matches[0];
902 }
903
904 /**
905 * Determine if a given string matches a given pattern.
906 *
907 * @param string|iterable<string> $pattern
908 * @param string $value
909 * @return bool
910 */
911 public static function isMatch($pattern, $value)
912 {
913 $value = (string) $value;
914
915 if (! is_iterable($pattern)) {
916 $pattern = [$pattern];
917 }
918
919 foreach ($pattern as $pattern) {
920 $pattern = (string) $pattern;
921
922 if (preg_match($pattern, $value) === 1) {
923 return true;
924 }
925 }
926
927 return false;
928 }
929
930 /**
931 * Get the string matching the given pattern.
932 *
933 * @param string $pattern
934 * @param string $subject
935 * @return \FluentCommunity\Framework\Support\Collection
936 */
937 public static function matchAll($pattern, $subject)
938 {
939 preg_match_all($pattern, $subject, $matches);
940
941 if (empty($matches[0])) {
942 return Helper::collect();
943 }
944
945 return Helper::collect($matches[1] ?? $matches[0]);
946 }
947
948 /**
949 * Pad both sides of a string with another.
950 *
951 * @param string $value
952 * @param int $length
953 * @param string $pad
954 * @return string
955 */
956 public static function padBoth($value, $length, $pad = ' ')
957 {
958 return str_pad($value, strlen($value) - mb_strlen($value) + $length, $pad, STR_PAD_BOTH);
959 }
960
961 /**
962 * Pad the left side of a string with another.
963 *
964 * @param string $value
965 * @param int $length
966 * @param string $pad
967 * @return string
968 */
969 public static function padLeft($value, $length, $pad = ' ')
970 {
971 return str_pad($value, strlen($value) - mb_strlen($value) + $length, $pad, STR_PAD_LEFT);
972 }
973
974 /**
975 * Pad the right side of a string with another.
976 *
977 * @param string $value
978 * @param int $length
979 * @param string $pad
980 * @return string
981 */
982 public static function padRight($value, $length, $pad = ' ')
983 {
984 return str_pad($value, strlen($value) - mb_strlen($value) + $length, $pad, STR_PAD_RIGHT);
985 }
986
987 /**
988 * Parse a Class[@]method style callback into class and method.
989 *
990 * @param string $callback
991 * @param string|null $default
992 * @return array<int, string|null>
993 */
994 public static function parseCallback($callback, $default = null)
995 {
996 return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
997 }
998
999 /**
1000 * Parse an integer from a string.
1001 *
1002 * @param string $value
1003 * @return int|null
1004 */
1005 public static function parseInt($value)
1006 {
1007 if (preg_match('/[-+]?\d+/', $value, $matches)) {
1008 return (int) $matches[0];
1009 }
1010 }
1011
1012 /**
1013 * Parse a floasting point number from a string.
1014 *
1015 * @param string $value
1016 * @return float|null
1017 */
1018 public static function parseFloat($value)
1019 {
1020 if (preg_match('/[-+]?\d*\.?\d+(e[-+]?\d+)?/i', $value, $matches)) {
1021 return (float) $matches[0];
1022 }
1023 }
1024
1025 /**
1026 * Remove all non-numeric characters from a string.
1027 *
1028 * @param string $value
1029 * @return string
1030 */
1031 public static function parseNumber($value)
1032 {
1033 return preg_replace('/[^0-9]/', '', $value);
1034 }
1035
1036 /**
1037 * Get the plural form of an English word.
1038 *
1039 * @param string $value
1040 * @param int|array|\Countable $count
1041 * @return string
1042 */
1043 public static function plural($value, $count = 2)
1044 {
1045 return Pluralizer::plural($value, $count);
1046 }
1047
1048 /**
1049 * Pluralize the last word of an English, studly caps case string.
1050 *
1051 * @param string $value
1052 * @param int|array|\Countable $count
1053 * @return string
1054 */
1055 public static function pluralStudly($value, $count = 2)
1056 {
1057 $parts = preg_split('/(.)(?=[A-Z])/u', $value, -1, PREG_SPLIT_DELIM_CAPTURE);
1058
1059 $lastWord = array_pop($parts);
1060
1061 return implode('', $parts).self::plural($lastWord, $count);
1062 }
1063
1064 /**
1065 * Find the multi-byte safe position of the first
1066 * occurrence of a given substring in a string.
1067 *
1068 * @param string $haystack
1069 * @param string $needle
1070 * @param int $offset
1071 * @param string|null $encoding
1072 * @return int|false
1073 */
1074 public static function position(
1075 $haystack, $needle, $offset = 0, $encoding = null
1076 )
1077 {
1078 return mb_strpos($haystack, (string) $needle, $offset, $encoding);
1079 }
1080
1081 /**
1082 * Generate a more truly "random" alpha-numeric string.
1083 *
1084 * @param int $length
1085 * @return string
1086 */
1087 public static function random($length = 16)
1088 {
1089 $string = '';
1090
1091 while (($len = strlen($string)) < $length) {
1092 $size = $length - $len;
1093
1094 $bytes = random_bytes($size);
1095
1096 $string .= substr(
1097 str_replace(
1098 ['/', '+', '='], '', base64_encode($bytes)
1099 ), 0, $size
1100 );
1101 }
1102
1103 return $string;
1104 }
1105
1106 /**
1107 * Repeat the given string.
1108 *
1109 * @param string $string
1110 * @param int $times
1111 * @return string
1112 */
1113 public static function repeat(string $string, int $times)
1114 {
1115 return str_repeat($string, $times);
1116 }
1117
1118 /**
1119 * Escape SQL LIKE wildcards (% and _) in a user-supplied search term.
1120 *
1121 * Pairs with `where('col', 'LIKE', '%'.Str::likeEscape($term).'%')` or
1122 * `whereLike($col, $term)`. Uses `\` as the escape character — works as
1123 * a no-op for MySQL/MariaDB (default LIKE escape is `\`); on SQLite or
1124 * PostgreSQL, also append `ESCAPE '\\'` to the LIKE clause so the
1125 * escapes take effect.
1126 *
1127 * @param string $value
1128 * @return string
1129 */
1130 public static function likeEscape($value)
1131 {
1132 return addcslashes((string) $value, '%_\\');
1133 }
1134
1135 /**
1136 * Replace a given value in the string sequentially with an array.
1137 *
1138 * @param string $search
1139 * @param array<int|string, string> $replace
1140 * @param string $subject
1141 * @return string
1142 */
1143 public static function replaceArray($search, array $replace, $subject)
1144 {
1145 $segments = explode($search, $subject);
1146
1147 $result = array_shift($segments);
1148
1149 foreach ($segments as $segment) {
1150 $result .= (array_shift($replace) ?? $search).$segment;
1151 }
1152
1153 return $result;
1154 }
1155
1156 /**
1157 * Replace the given value in the given string.
1158 *
1159 * @param string|iterable<string> $search
1160 * @param string|iterable<string> $replace
1161 * @param string|iterable<string> $subject
1162 * @param bool $caseSensitive
1163 * @return string|string[]
1164 */
1165 public static function replace($search, $replace, $subject, $caseSensitive = true)
1166 {
1167 if ($search instanceof Traversable) {
1168 $search = (new Collection($search))->all();
1169 }
1170
1171 if ($replace instanceof Traversable) {
1172 $replace = (new Collection($replace))->all();
1173 }
1174
1175 if ($subject instanceof Traversable) {
1176 $subject = (new Collection($subject))->all();
1177 }
1178
1179 return $caseSensitive
1180 ? str_replace($search, $replace, $subject)
1181 : str_ireplace($search, $replace, $subject);
1182 }
1183
1184 /**
1185 * Replace the first occurrence of a given value in the string.
1186 *
1187 * @param string $search
1188 * @param string $replace
1189 * @param string $subject
1190 * @return string
1191 */
1192 public static function replaceFirst($search, $replace, $subject)
1193 {
1194 if ($search === '') {
1195 return $subject;
1196 }
1197
1198 $position = strpos($subject, $search);
1199
1200 if ($position !== false) {
1201 return substr_replace($subject, $replace, $position, strlen($search));
1202 }
1203
1204 return $subject;
1205 }
1206
1207 /**
1208 * Replace the first occurrence of the given value if it appears at the start of the string.
1209 *
1210 * @param string $search
1211 * @param string $replace
1212 * @param string $subject
1213 * @return string
1214 */
1215 public static function replaceStart($search, $replace, $subject)
1216 {
1217 $search = (string) $search;
1218
1219 if ($search === '') {
1220 return $subject;
1221 }
1222
1223 if (static::startsWith($subject, $search)) {
1224 return static::replaceFirst($search, $replace, $subject);
1225 }
1226
1227 return $subject;
1228 }
1229
1230 /**
1231 * Replace the last occurrence of a given value in the string.
1232 *
1233 * @param string $search
1234 * @param string $replace
1235 * @param string $subject
1236 * @return string
1237 */
1238 public static function replaceLast($search, $replace, $subject)
1239 {
1240 if ($search === '') {
1241 return $subject;
1242 }
1243
1244 $position = strrpos($subject, $search);
1245
1246 if ($position !== false) {
1247 return substr_replace($subject, $replace, $position, strlen($search));
1248 }
1249
1250 return $subject;
1251 }
1252
1253 /**
1254 * Replace the last occurrence of a given value if it appears at the end of the string.
1255 *
1256 * @param string $search
1257 * @param string $replace
1258 * @param string $subject
1259 * @return string
1260 */
1261 public static function replaceEnd($search, $replace, $subject)
1262 {
1263 $search = (string) $search;
1264
1265 if ($search === '') {
1266 return $subject;
1267 }
1268
1269 if (static::endsWith($subject, $search)) {
1270 return static::replaceLast($search, $replace, $subject);
1271 }
1272
1273 return $subject;
1274 }
1275
1276 /**
1277 * Replace the patterns matching the given regular expression.
1278 *
1279 * @param array|string $pattern
1280 * @param \Closure|string[]|string $replace
1281 * @param array|string $subject
1282 * @param int $limit
1283 * @return string|string[]|null
1284 */
1285 public static function replaceMatches($pattern, $replace, $subject, $limit = -1)
1286 {
1287 if ($replace instanceof \Closure) {
1288 return preg_replace_callback($pattern, $replace, $subject, $limit);
1289 }
1290
1291 return preg_replace($pattern, $replace, $subject, $limit);
1292 }
1293
1294 /**
1295 * Remove any occurrence of the given string in the subject.
1296 *
1297 * @param string|array<string> $search
1298 * @param string $subject
1299 * @param bool $caseSensitive
1300 * @return string
1301 */
1302 public static function remove($search, $subject, $caseSensitive = true)
1303 {
1304 $subject = $caseSensitive
1305 ? str_replace($search, '', $subject)
1306 : str_ireplace($search, '', $subject);
1307
1308 return $subject;
1309 }
1310
1311 /**
1312 * Reverse the given string.
1313 *
1314 * @param string $value
1315 * @return string
1316 */
1317 public static function reverse(string $value)
1318 {
1319 return implode(array_reverse(mb_str_split($value)));
1320 }
1321
1322 /**
1323 * Begin a string with a single instance of a given value.
1324 *
1325 * @param string $value
1326 * @param string $prefix
1327 * @return string
1328 */
1329 public static function start($value, $prefix)
1330 {
1331 $quoted = preg_quote($prefix, '/');
1332
1333 return $prefix.preg_replace('/^(?:'.$quoted.')+/u', '', $value);
1334 }
1335
1336 /**
1337 * Convert the given string to upper-case.
1338 *
1339 * @param string $value
1340 * @return string
1341 */
1342 public static function upper($value)
1343 {
1344 return mb_strtoupper($value, 'UTF-8');
1345 }
1346
1347 /**
1348 * Convert the given string to title case.
1349 *
1350 * @param string $value
1351 * @return string
1352 */
1353 public static function title($value)
1354 {
1355 return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
1356 }
1357
1358 /**
1359 * Convert the given string to title case for each word.
1360 *
1361 * @param string $value
1362 * @return string
1363 */
1364 public static function headline($value)
1365 {
1366 $parts = explode(' ', $value);
1367
1368 $parts = count($parts) > 1
1369 ? $parts = array_map([static::class, 'title'], $parts)
1370 : $parts = array_map([static::class, 'title'], static::ucsplit(implode('_', $parts)));
1371
1372 $collapsed = static::replace(['-', '_', ' '], '_', implode('_', $parts));
1373
1374 return implode(' ', array_filter(explode('_', $collapsed)));
1375 }
1376
1377 /**
1378 * Convert the given string to APA-style title case.
1379 *
1380 * See: https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case
1381 *
1382 * @param string $value
1383 * @return string
1384 */
1385 public static function apa($value)
1386 {
1387 if (trim($value) === '') {
1388 return $value;
1389 }
1390
1391 $minorWords = [
1392 'and', 'as', 'but', 'for', 'if', 'nor', 'or', 'so', 'yet', 'a', 'an',
1393 'the', 'at', 'by', 'for', 'in', 'of', 'off', 'on', 'per', 'to', 'up', 'via',
1394 'et', 'ou', 'un', 'une', 'la', 'le', 'les', 'de', 'du', 'des', 'par', 'à',
1395 ];
1396
1397 $endPunctuation = ['.', '!', '?', ':', '', ','];
1398
1399 $words = preg_split('/\s+/', $value, -1, PREG_SPLIT_NO_EMPTY);
1400
1401 for ($i = 0; $i < count($words); $i++) {
1402 $lowercaseWord = mb_strtolower($words[$i]);
1403
1404 if (str_contains($lowercaseWord, '-')) {
1405 $hyphenatedWords = explode('-', $lowercaseWord);
1406
1407 $hyphenatedWords = array_map(function ($part) use ($minorWords) {
1408 return (in_array($part, $minorWords) && mb_strlen($part) <= 3)
1409 ? $part
1410 : mb_strtoupper(mb_substr($part, 0, 1)).mb_substr($part, 1);
1411 }, $hyphenatedWords);
1412
1413 $words[$i] = implode('-', $hyphenatedWords);
1414 } else {
1415 if (in_array($lowercaseWord, $minorWords) &&
1416 mb_strlen($lowercaseWord) <= 3 &&
1417 ! ($i === 0 || in_array(mb_substr($words[$i - 1], -1), $endPunctuation))) {
1418 $words[$i] = $lowercaseWord;
1419 } else {
1420 $words[$i] = mb_strtoupper(mb_substr($lowercaseWord, 0, 1)).mb_substr($lowercaseWord, 1);
1421 }
1422 }
1423 }
1424
1425 return implode(' ', $words);
1426 }
1427
1428 /**
1429 * Get the singular form of an English word.
1430 *
1431 * @param string $value
1432 * @return string
1433 */
1434 public static function singular($value)
1435 {
1436 return Pluralizer::singular($value);
1437 }
1438
1439 /**
1440 * Generate a URL friendly "slug" from a given string.
1441 *
1442 * @param string $title
1443 * @return string
1444 * @see https://developer.wordpress.org/reference/functions/sanitize_title/
1445 */
1446 public static function slug($title, $fallback_title = '', $context = 'save')
1447 {
1448 return sanitize_title($title, $fallback_title, $context);
1449 }
1450
1451 /**
1452 * Convert a string to snake case.
1453 *
1454 * @param string $value
1455 * @param string $delimiter
1456 * @return string
1457 */
1458 public static function snake($value, $delimiter = '_')
1459 {
1460 $key = $value;
1461
1462 if (isset(static::$snakeCache[$key][$delimiter])) {
1463 return static::$snakeCache[$key][$delimiter];
1464 }
1465
1466 if (! ctype_lower($value)) {
1467 $value = preg_replace('/\s+/u', '', ucwords($value));
1468
1469 $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value));
1470 }
1471
1472 return static::$snakeCache[$key][$delimiter] = $value;
1473 }
1474
1475 /**
1476 * Remove whitespace (including special Unicode spaces) from a string.
1477 *
1478 * Supports trimming from left, right, or both ends, and allows
1479 * specifying additional characters to trim.
1480 *
1481 * @param string $value The string to trim.
1482 * @param string|null $charlist Optional additional characters to trim.
1483 * @param string $mode One of 'both' (default), 'left', 'right'.
1484 *
1485 * @return string The trimmed string.
1486 */
1487 protected static function _unicodeTrim(string $value, ?string $charlist = null, string $mode = 'both'): string
1488 {
1489 $defaultChars = " \n\r\t\v"; // \0 omitted to avoid null byte errors
1490
1491 $chars = $charlist ?? $defaultChars;
1492 $quoted = preg_quote($chars, '~');
1493
1494 // Unicode invisible spaces we want to include
1495 $unicodeSpaces = '\s\x{FEFF}\x{200B}\x{200E}';
1496
1497 switch ($mode) {
1498 case 'left':
1499 $pattern = '~^[' . $unicodeSpaces . $quoted . ']+~u';
1500 break;
1501 case 'right':
1502 $pattern = '~[' . $unicodeSpaces . $quoted . ']+$~u';
1503 break;
1504 case 'both':
1505 default:
1506 $pattern = '~^[' . $unicodeSpaces . $quoted . ']+|[' . $unicodeSpaces . $quoted . ']+$~u';
1507 break;
1508 }
1509
1510 return preg_replace($pattern, '', $value) ?? $value;
1511 }
1512
1513 /**
1514 * Remove all whitespace (including special Unicode spaces) from both ends of a string.
1515 *
1516 * @param string $value The string to trim.
1517 * @param string|null $charlist Optional additional characters to trim.
1518 *
1519 * @return string The trimmed string.
1520 */
1521 public static function trim(string $value, ?string $charlist = null): string
1522 {
1523 return static::_unicodeTrim($value, $charlist, 'both');
1524 }
1525
1526 /**
1527 * Remove all whitespace (including special Unicode spaces) from the beginning of a string.
1528 *
1529 * @param string $value The string to trim.
1530 * @param string|null $charlist Optional additional characters to trim.
1531 *
1532 * @return string The trimmed string.
1533 */
1534 public static function ltrim(string $value, ?string $charlist = null): string
1535 {
1536 return static::_unicodeTrim($value, $charlist, 'left');
1537 }
1538
1539 /**
1540 * Remove all whitespace (including special Unicode spaces) from the end of a string.
1541 *
1542 * @param string $value The string to trim.
1543 * @param string|null $charlist Optional additional characters to trim.
1544 *
1545 * @return string The trimmed string.
1546 */
1547 public static function rtrim(string $value, ?string $charlist = null): string
1548 {
1549 return static::_unicodeTrim($value, $charlist, 'right');
1550 }
1551
1552 /**
1553 * Remove all "extra" blank space from the given string.
1554 *
1555 * @param string $value
1556 * @return string
1557 */
1558 public static function squish($value)
1559 {
1560 return preg_replace('~(\s|\x{3164}|\x{1160})+~u', ' ', preg_replace('~^[\s\x{FEFF}]+|[\s\x{FEFF}]+$~u', '', $value));
1561 }
1562
1563 /**
1564 * Determine if a given string starts with a given substring.
1565 *
1566 * @param string $haystack
1567 * @param string|iterable<string> $needles
1568 * @return bool
1569 */
1570 public static function startsWith($haystack, $needles)
1571 {
1572 if (!is_iterable($needles)) {
1573 $needles = [$needles];
1574 }
1575
1576 if ($haystack === null) {
1577 return false;
1578 }
1579
1580 foreach ($needles as $needle) {
1581 if ((string) $needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0) {
1582 return true;
1583 }
1584 }
1585
1586 return false;
1587 }
1588
1589 /**
1590 * Convert a value to studly caps case.
1591 *
1592 * @param string $value
1593 * @return string
1594 */
1595 public static function studly($value)
1596 {
1597 $key = $value;
1598
1599 if (isset(static::$studlyCache[$key])) {
1600 return static::$studlyCache[$key];
1601 }
1602
1603 $words = explode(' ', static::replace(['-', '_'], ' ', $value));
1604
1605 $studlyWords = array_map(function ($word) {
1606 return static::ucfirst($word);
1607 }, $words);
1608
1609 return static::$studlyCache[$key] = implode($studlyWords);
1610 }
1611
1612 /**
1613 * Convert a value to Pascal case.
1614 *
1615 * @param string $value
1616 * @return string
1617 */
1618 public static function pascal($value)
1619 {
1620 return static::studly($value);
1621 }
1622
1623 /**
1624 * Returns the portion of the string specified by the start and length parameters.
1625 *
1626 * @param string $string
1627 * @param int $start
1628 * @param int|null $length
1629 * @return string
1630 */
1631 public static function substr($string, $start, $length = null)
1632 {
1633 return mb_substr($string, $start, $length, 'UTF-8');
1634 }
1635
1636 /**
1637 * Returns the number of substring occurrences.
1638 *
1639 * @param string $haystack
1640 * @param string $needle
1641 * @param int $offset
1642 * @param int|null $length
1643 * @return int
1644 */
1645 public static function substrCount(
1646 $haystack, $needle, $offset = 0, $length = null
1647 ) {
1648 if (! is_null($length)) {
1649 return substr_count($haystack, $needle, $offset, $length);
1650 } else {
1651 return substr_count($haystack, $needle, $offset);
1652 }
1653 }
1654
1655 /**
1656 * Replace text within a portion of a string.
1657 *
1658 * @param string|array $string
1659 * @param string|array $replace
1660 * @param array|int $offset
1661 * @param array|int|null $length
1662 * @return string|array
1663 */
1664 public static function substrReplace(
1665 $string, $replace, $offset = 0, $length = null
1666 ) {
1667 if ($length === null) {
1668 $length = strlen($string);
1669 }
1670
1671 return substr_replace($string, $replace, $offset, $length);
1672 }
1673
1674 /**
1675 * Swap multiple keywords in a string with other keywords.
1676 *
1677 * @param array $map
1678 * @param string $subject
1679 * @return string
1680 */
1681 public static function swap(array $map, $subject)
1682 {
1683 return strtr($subject, $map);
1684 }
1685
1686 /**
1687 * Take the first or last {$limit} characters of a string.
1688 *
1689 * @param string $string
1690 * @param int $limit
1691 * @return string
1692 */
1693 public static function take($string, int $limit): string
1694 {
1695 if ($limit < 0) {
1696 return static::substr($string, $limit);
1697 }
1698
1699 return static::substr($string, 0, $limit);
1700 }
1701
1702 /**
1703 * Convert the given string to Base64 encoding.
1704 *
1705 * @param string $string
1706 * @return string
1707 */
1708 public static function toBase64($string)
1709 {
1710 return base64_encode($string);
1711 }
1712
1713 /**
1714 * Decode the given Base64 encoded string.
1715 *
1716 * @param string $string
1717 * @param bool $strict
1718 * @return string|false
1719 */
1720 public static function fromBase64($string, $strict = false)
1721 {
1722 return base64_decode($string, $strict);
1723 }
1724
1725 /**
1726 * Make a string's first character lowercase.
1727 *
1728 * @param string $string
1729 * @return string
1730 */
1731 public static function lcfirst($string)
1732 {
1733 return static::lower(
1734 static::substr($string, 0, 1)
1735 ).static::substr($string, 1);
1736 }
1737
1738 /**
1739 * Make a string's first character uppercase.
1740 *
1741 * @param string $string
1742 * @return string
1743 */
1744 public static function ucfirst($string)
1745 {
1746 return static::upper(
1747 static::substr($string, 0, 1)
1748 ).static::substr($string, 1);
1749 }
1750
1751 /**
1752 * Split a string into pieces by uppercase characters.
1753 *
1754 * @param string $string
1755 * @return array
1756 */
1757 public static function ucsplit($string)
1758 {
1759 return preg_split('/(?=\p{Lu})/u', $string, -1, PREG_SPLIT_NO_EMPTY);
1760 }
1761
1762 /**
1763 * Count the number of words in a UTF-8 string.
1764 *
1765 * @param string $string
1766 * @return int
1767 */
1768 public static function wordCount(string $string)
1769 {
1770 $words = preg_split('/[^\p{L}\p{N}]+/u', $string, -1, PREG_SPLIT_NO_EMPTY);
1771
1772 return count($words);
1773 }
1774
1775 /**
1776 * Wrap a string to a given number of characters.
1777 *
1778 * @param string $string
1779 * @param int $characters
1780 * @param string $break
1781 * @param bool $cutLongWords
1782 * @return string
1783 */
1784 public static function wordWrap(
1785 $string,
1786 $characters = 75,
1787 $break = "\n",
1788 $cutLongWords = false
1789 )
1790 {
1791 return wordwrap($string, $characters, $break, $cutLongWords);
1792 }
1793
1794 /**
1795 * Wrap the string with the given strings.
1796 *
1797 * @param string $value
1798 * @param string $before
1799 * @param string|null $after
1800 * @return string
1801 */
1802 public static function wrap($value, $before, $after = null)
1803 {
1804 return $before.$value.($after = $after ?: $before);
1805 }
1806
1807 /**
1808 * Generate a UUID (version 4).
1809 *
1810 * @return string
1811 */
1812 public static function uuid()
1813 {
1814 return wp_generate_uuid4();
1815 }
1816
1817 /**
1818 * Increment a counter.
1819 * @return int
1820 */
1821 public static function incrementCounter($prefix = '')
1822 {
1823 return wp_unique_id($prefix);
1824 }
1825
1826 /**
1827 * Generate a deterministic unique id by a prefix.
1828 *
1829 * @param string $slug
1830 * @return string
1831 */
1832 public static function incrementBySlug($slug = '')
1833 {
1834 return wp_unique_prefixed_id(
1835 $slug = $slug ?: App::config()->get('app.slug')
1836 );
1837 }
1838
1839 /**
1840 * Remove all strings from the casing caches.
1841 *
1842 * @return void
1843 */
1844 public static function flushCache()
1845 {
1846 static::$snakeCache = [];
1847 static::$camelCache = [];
1848 static::$studlyCache = [];
1849 }
1850 }
1851