PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk 1.2.0 All 47 releases
fluent-cart / vendor / wpfluent / framework / src / WPFluent / Support / Str.php

Str.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at vendor/wpfluent/framework/src/WPFluent/Support/Str.php

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