1 | <?php |
||||
2 | |||||
3 | /** |
||||
4 | * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0 |
||||
5 | * @copyright Aimeos (aimeos.org), 2020-2025 |
||||
6 | * @package Base |
||||
7 | */ |
||||
8 | |||||
9 | |||||
10 | namespace Aimeos\Base; |
||||
11 | |||||
12 | |||||
13 | /** |
||||
14 | * String utility class |
||||
15 | * |
||||
16 | * @package Base |
||||
17 | */ |
||||
18 | class Str |
||||
19 | { |
||||
20 | use \Aimeos\Macro\Macroable; |
||||
21 | |||||
22 | |||||
23 | private static $node; |
||||
24 | private static $seq = 0; |
||||
25 | |||||
26 | |||||
27 | /** |
||||
28 | * Returns the sub-string after the given needle. |
||||
29 | * |
||||
30 | * @param mixed $str Stringable value |
||||
31 | * @param mixed $needles String or strings to search for |
||||
32 | * @return string|null Sub-string after the needle or NULL if needle is not part of the string |
||||
33 | */ |
||||
34 | public static function after( $str, $needles ) : ?string |
||||
35 | { |
||||
36 | $str = (string) $str; |
||||
37 | |||||
38 | foreach( (array) $needles as $needle ) |
||||
39 | { |
||||
40 | $needle = (string) $needle; |
||||
41 | |||||
42 | if( ( $len = strlen( $needle ) ) > 0 && ( $pos = strpos( $str, $needle ) ) !== false |
||||
43 | && ( $result = substr( $str, $pos + $len ) ) !== false |
||||
44 | ) { |
||||
45 | return $result; |
||||
46 | } |
||||
47 | } |
||||
48 | |||||
49 | return null; |
||||
50 | } |
||||
51 | |||||
52 | |||||
53 | /** |
||||
54 | * Returns the sub-string before the given needle. |
||||
55 | * |
||||
56 | * @param mixed $str Stringable value |
||||
57 | * @param mixed $needles String or strings to search for |
||||
58 | * @return string|null Sub-string before the needle or NULL if needle is not part of the string |
||||
59 | */ |
||||
60 | public static function before( $str, $needles ) : ?string |
||||
61 | { |
||||
62 | $str = (string) $str; |
||||
63 | |||||
64 | foreach( (array) $needles as $needle ) |
||||
65 | { |
||||
66 | $needle = (string) $needle; |
||||
67 | |||||
68 | if( $needle !== '' && ( $pos = strpos( $str, $needle ) ) !== false |
||||
69 | && ( $result = substr( $str, 0, $pos ) ) !== false |
||||
70 | ) { |
||||
71 | return $result; |
||||
72 | } |
||||
73 | } |
||||
74 | |||||
75 | return null; |
||||
76 | } |
||||
77 | |||||
78 | |||||
79 | /** |
||||
80 | * Replaces special HTML characters by their entities. |
||||
81 | * |
||||
82 | * @param mixed $str Stringable value |
||||
83 | * @param int $flags Which characters to encode |
||||
84 | * @return string String which isn't interpreted as HTML and save to include in HTML documents |
||||
85 | */ |
||||
86 | public static function decode( $str, int $flags = ENT_SUBSTITUTE | ENT_QUOTES | ENT_HTML5 ) : string |
||||
87 | { |
||||
88 | return str_replace( '`', '`', htmlspecialchars_decode( (string) $str, $flags | ENT_QUOTES ) ); |
||||
89 | } |
||||
90 | |||||
91 | |||||
92 | /** |
||||
93 | * Tests if the strings ends with the needle. |
||||
94 | * |
||||
95 | * @param mixed $str Stringable value |
||||
96 | * @param array|string|null $needles String/character or list thereof to compare with |
||||
97 | * @return bool TRUE if string ends with needle, FALSE if not |
||||
98 | */ |
||||
99 | public static function ends( $str, $needles ) : bool |
||||
100 | { |
||||
101 | $str = (string) $str; |
||||
102 | |||||
103 | if( $str === '' || $needles == null ) { |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
104 | return false; |
||||
105 | } |
||||
106 | |||||
107 | foreach( (array) $needles as $needle ) |
||||
108 | { |
||||
109 | $needle = (string) $needle; |
||||
110 | |||||
111 | if( $needle !== '' && substr_compare( $str, $needle, -strlen( $needle ) ) === 0 ) { |
||||
112 | return true; |
||||
113 | } |
||||
114 | } |
||||
115 | |||||
116 | return false; |
||||
117 | } |
||||
118 | |||||
119 | |||||
120 | /** |
||||
121 | * Replaces special HTML characters by their entities. |
||||
122 | * |
||||
123 | * @param mixed $str Stringable value |
||||
124 | * @param int $flags Which characters to encode |
||||
125 | * @return string String which isn't interpreted as HTML and save to include in HTML documents |
||||
126 | */ |
||||
127 | public static function html( $str, int $flags = ENT_SUBSTITUTE | ENT_QUOTES | ENT_HTML5 ) : string |
||||
128 | { |
||||
129 | return htmlspecialchars( (string) $str, $flags, 'UTF-8' ); |
||||
130 | } |
||||
131 | |||||
132 | |||||
133 | /** |
||||
134 | * Tests if the strings contains all of the needles. |
||||
135 | * |
||||
136 | * @param mixed $str Stringable value |
||||
137 | * @param string|array|null $needles String/character or list thereof to search for |
||||
138 | * @return bool TRUE if string contains all needles, FALSE if not |
||||
139 | */ |
||||
140 | public static function in( $str, $needles ) : bool |
||||
141 | { |
||||
142 | $str = (string) $str; |
||||
143 | |||||
144 | if( $needles == null ) { |
||||
0 ignored issues
–
show
|
|||||
145 | return false; |
||||
146 | } |
||||
147 | |||||
148 | foreach( (array) $needles as $needle ) |
||||
149 | { |
||||
150 | $needle = (string) $needle; |
||||
151 | |||||
152 | if( $needle === '' || strpos( $str, $needle ) === false ) { |
||||
153 | return false; |
||||
154 | } |
||||
155 | } |
||||
156 | |||||
157 | return true; |
||||
158 | } |
||||
159 | |||||
160 | |||||
161 | /** |
||||
162 | * Transforms the string into a suitable URL segment. |
||||
163 | * |
||||
164 | * @param mixed $str Stringable value |
||||
165 | * @param string $lang Two letter ISO language code |
||||
166 | * @param string $sep Separator between the words |
||||
167 | * @return string String suitable for an URL segment |
||||
168 | */ |
||||
169 | public static function slug( $str, string $lang = 'en', string $sep = '-' ) : string |
||||
170 | { |
||||
171 | if( $fcn = static::macro( 'slug' ) ) { |
||||
172 | return $fcn( $str, $lang, $sep ); |
||||
173 | } |
||||
174 | |||||
175 | $str = \voku\helper\ASCII::to_ascii( (string) $str, $lang ); |
||||
176 | $str = strtolower( preg_replace( '/[^A-Za-z0-9\_\-\~\.]+/', $sep, $str ) ); |
||||
177 | return trim( preg_replace( '/(' . preg_quote( $sep, '/' ) . '){2,}/', $sep, $str ), $sep ); |
||||
178 | } |
||||
179 | |||||
180 | |||||
181 | /** |
||||
182 | * Tests if the strings contains at least one of the needles. |
||||
183 | * |
||||
184 | * @param mixed $str Stringable value |
||||
185 | * @param array $needles Strings or characters to search for |
||||
186 | * @return bool TRUE if string contains at least one needle, FALSE if it contains none |
||||
187 | */ |
||||
188 | public static function some( $str, array $needles ) : bool |
||||
189 | { |
||||
190 | $str = (string) $str; |
||||
191 | |||||
192 | foreach( $needles as $needle ) |
||||
193 | { |
||||
194 | $needle = (string) $needle; |
||||
195 | |||||
196 | if( $needle !== '' && strpos( $str, $needle ) !== false ) { |
||||
197 | return true; |
||||
198 | } |
||||
199 | } |
||||
200 | |||||
201 | return false; |
||||
202 | } |
||||
203 | |||||
204 | |||||
205 | /** |
||||
206 | * Tests if the strings starts with the needle. |
||||
207 | * |
||||
208 | * @param mixed $str Stringable value |
||||
209 | * @param array|string|null $needles String/character or list thereof to compare with |
||||
210 | * @return bool TRUE if string starts with needle, FALSE if not |
||||
211 | */ |
||||
212 | public static function starts( $str, $needles ) : bool |
||||
213 | { |
||||
214 | $str = (string) $str; |
||||
215 | |||||
216 | if( $str === '' || $needles == null ) { |
||||
0 ignored issues
–
show
|
|||||
217 | return false; |
||||
218 | } |
||||
219 | |||||
220 | foreach( (array) $needles as $needle ) |
||||
221 | { |
||||
222 | $needle = (string) $needle; |
||||
223 | |||||
224 | if( $needle !== '' && strncmp( $str, $needle, strlen( $needle ) ) === 0 ) { |
||||
225 | return true; |
||||
226 | } |
||||
227 | } |
||||
228 | |||||
229 | return false; |
||||
230 | } |
||||
231 | |||||
232 | |||||
233 | /** |
||||
234 | * Returns a date formatted according to the given format string. |
||||
235 | * |
||||
236 | * @param string $format DateTime format string but with format characters preceded by "%" |
||||
237 | * @return string String with date/time according to the format string |
||||
238 | */ |
||||
239 | public static function strtime( string $format ) : string |
||||
240 | { |
||||
241 | $format = str_replace( ['%M', '%S'], ['%i', '%s'], $format ); |
||||
242 | return trim( date_create()->format( str_replace( '\\%\\', '', '\\' . join( '\\', str_split( $format ) ) ) ) ); |
||||
0 ignored issues
–
show
It seems like
str_split($format) can also be of type true ; however, parameter $array of join() does only seem to accept array|null , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
243 | } |
||||
244 | |||||
245 | |||||
246 | /** |
||||
247 | * Generates a unique ID string suitable as global identifier |
||||
248 | * |
||||
249 | * The ID is similar to an UUID and is as unique as an UUID but it's human |
||||
250 | * readable string is only 20 bytes long. Additionally, the unique ID is |
||||
251 | * optimized for being used as primary key in databases. |
||||
252 | * |
||||
253 | * @return string Global unique ID of 20 bytes length |
||||
254 | */ |
||||
255 | public static function uid() : string |
||||
256 | { |
||||
257 | if( self::$node === null ) |
||||
258 | { |
||||
259 | try { |
||||
260 | self::$node = random_bytes( 6 ); |
||||
261 | } catch( \Throwable $t ) { |
||||
262 | if( function_exists( 'openssl_random_pseudo_bytes' ) ) { |
||||
263 | self::$node = openssl_random_pseudo_bytes( 6 ); |
||||
264 | } else { |
||||
265 | self::$node = pack( 'n*', rand( 0, 0xffff ), rand( 0, 0xffff ), rand( 0, 0xffff ) ); |
||||
266 | } |
||||
267 | } |
||||
268 | } |
||||
269 | |||||
270 | $time = microtime( true ); |
||||
271 | $sec = (int) $time; |
||||
272 | $usec = (int) ( fmod( $time, 1 ) * 1000000 ); |
||||
0 ignored issues
–
show
It seems like
$time can also be of type string ; however, parameter $num1 of fmod() does only seem to accept double , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
273 | |||||
274 | self::$seq = self::$seq + 1 & 0xfff; // 20 bits for sequence (1 to 4,095), wraps around |
||||
275 | |||||
276 | $hsec = ( $sec & 0xff00000000 ) >> 32; // 5th byte from seconds till 1970-01-01T00:00:00 (on 64 bit platforms) |
||||
277 | $lsec = $sec & 0xffffffff; // Lowest 32 bits from seconds till 1970-01-01T00:00:00 |
||||
278 | $husec = ( $usec & 0xffff0 ) >> 4; // Highest 16 bits from micro seconds (total 20 bits) |
||||
279 | $mix = ( $usec & 0xf ) << 4 | ( self::$seq & 0xf00 ) >> 8; // Lowest 4 bits (usec) and highest 4 bits (seq) |
||||
280 | $lseq = self::$seq & 0xff; // Lowest 16 bits from sequence |
||||
281 | |||||
282 | // 5 bytes seconds, 2 byte usec, 1 byte usec+seq, 1 byte seq, 6 bytes node |
||||
283 | $uid = base64_encode( pack( 'CNnCC', $hsec, $lsec, $husec, $mix, $lseq ) . self::$node ); |
||||
284 | |||||
285 | return str_replace( ['+', '/'], ['-', '_'], $uid ); // URL safety |
||||
286 | } |
||||
287 | } |
||||
288 |