PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.7.1
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.7.1
2.5.0 2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 All 34 releases
fluent-booking / vendor / wpfluent / framework / src / WPFluent / Support / Str.php

Str.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 1.7.1, at vendor/wpfluent/framework/src/WPFluent/Support/Str.php

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