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