Passed
Push — master ( c8b0fe...42afb4 )
by Aimeos
05:38
created

Str::method()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 7
rs 10
1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2020-2021
6
 * @package MW
7
 */
8
9
10
namespace Aimeos\MW;
11
12
13
/**
14
 * String utility class
15
 *
16
 * @package MW
17
 */
18
class Str
19
{
20
	use \Aimeos\Macro\Macroable;
21
22
23
	private static $methods = [];
0 ignored issues
show
introduced by
The private property $methods is not used, and could be removed.
Loading history...
24
	private static $node;
25
	private static $seq = 0;
26
27
28
	/**
29
	 * Returns the sub-string after the given needle.
30
	 *
31
	 * @param mixed $str Stringable value
32
	 * @param mixed $needles String or strings to search for
33
	 * @return string|null Sub-string after the needle or NULL if needle is not part of the string
34
	 */
35
	public static function after( $str, $needles ) : ?string
36
	{
37
		$str = (string) $str;
38
39
		foreach( (array) $needles as $needle )
40
		{
41
			$needle = (string) $needle;
42
43
			if( ( $len = strlen( $needle ) ) > 0 && ( $pos = strpos( $str, $needle ) ) !== false
44
				&& ( $result = substr( $str, $pos + $len ) ) !== false
45
			) {
46
				return $result;
47
			}
48
		}
49
50
		return null;
51
	}
52
53
54
	/**
55
	 * Returns the sub-string before the given needle.
56
	 *
57
	 * @param mixed $str Stringable value
58
	 * @param mixed $needles String or strings to search for
59
	 * @return string|null Sub-string before the needle or NULL if needle is not part of the string
60
	 */
61
	public static function before( $str, $needles ) : ?string
62
	{
63
		$str = (string) $str;
64
65
		foreach( (array) $needles as $needle )
66
		{
67
			$needle = (string) $needle;
68
69
			if( $needle !== '' && ( $pos = strpos( $str, $needle ) ) !== false
70
				&& ( $result = substr( $str, 0, $pos ) ) !== false
71
			) {
72
				return $result;
73
			}
74
		}
75
76
		return null;
77
	}
78
79
80
	/**
81
	 * Tests if the strings ends with the needle.
82
	 *
83
	 * @param mixed $str Stringable value
84
	 * @param array|string|null $needles String/character or list thereof to compare with
85
	 * @return bool TRUE if string ends with needle, FALSE if not
86
	 */
87
	public static function ends( $str, $needles ) : bool
88
	{
89
		$str = (string) $str;
90
91
		if( $str === '' || $needles == null ) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $needles of type array|null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
92
			return false;
93
		}
94
95
		foreach( (array) $needles as $needle )
96
		{
97
			$needle = (string) $needle;
98
99
			if( $needle !== '' && substr_compare( $str, $needle, -strlen( $needle ) ) === 0 ) {
100
				return true;
101
			}
102
		}
103
104
		return false;
105
	}
106
107
108
	/**
109
	 * Replaces special HTML characters by their entities.
110
	 *
111
	 * @param mixed $str Stringable value
112
	 * @param int $flags Which characters to encode
113
	 * @return string String which isn't interpreted as HTML and save to include in HTML documents
114
	 */
115
	public static function html( $str, int $flags = ENT_COMPAT | ENT_HTML401 ) : string
116
	{
117
		return htmlspecialchars( (string) $str, $flags, 'UTF-8' );
118
	}
119
120
121
	/**
122
	 * Tests if the strings contains all of the needles.
123
	 *
124
	 * @param mixed $str Stringable value
125
	 * @param string|array|null $needles String/character or list thereof to search for
126
	 * @return bool TRUE if string contains all needles, FALSE if not
127
	 */
128
	public static function in( $str, $needles ) : bool
129
	{
130
		$str = (string) $str;
131
132
		if( $needles == null ) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $needles of type array|null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
133
			return false;
134
		}
135
136
		foreach( (array) $needles as $needle )
137
		{
138
			$needle = (string) $needle;
139
140
			if( $needle === '' || strpos( $str, $needle ) === false ) {
141
				return false;
142
			}
143
		}
144
145
		return true;
146
	}
147
148
149
	/**
150
	 * Transforms the string into a suitable URL segment.
151
	 *
152
	 * @param mixed $str Stringable value
153
	 * @param string $lang Two letter ISO language code
154
	 * @param string $sep Separator between the words
155
	 * @return string String suitable for an URL segment
156
	 */
157
	public static function slug( $str, string $lang = 'en', string $sep = '-' ) : string
158
	{
159
		if( $fcn = static::macro( 'slug' ) ) {
160
			return $fcn( $str, $lang, $sep );
161
		}
162
163
		$str = \voku\helper\ASCII::to_ascii( (string) $str, $lang );
164
		return trim( preg_replace( '/[^A-Za-z0-9~_.]+/', $sep, $str ), $sep );
165
	}
166
167
168
	/**
169
	 * Tests if the strings contains at least one of the needles.
170
	 *
171
	 * @param mixed $str Stringable value
172
	 * @param array $needles Strings or characters to search for
173
	 * @return bool TRUE if string contains at least one needle, FALSE if it contains none
174
	 */
175
	public static function some( $str, array $needles ) : bool
176
	{
177
		$str = (string) $str;
178
179
		foreach( $needles as $needle )
180
		{
181
			$needle = (string) $needle;
182
183
			if( $needle !== '' && strpos( $str, $needle ) !== false ) {
184
				return true;
185
			}
186
		}
187
188
		return false;
189
	}
190
191
192
	/**
193
	 * Tests if the strings starts with the needle.
194
	 *
195
	 * @param mixed $str Stringable value
196
	 * @param array|string|null $needles String/character or list thereof to compare with
197
	 * @return bool TRUE if string starts with needle, FALSE if not
198
	 */
199
	public static function starts( $str, $needles ) : bool
200
	{
201
		$str = (string) $str;
202
203
		if( $str === '' || $needles == null ) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $needles of type array|null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
204
			return false;
205
		}
206
207
		foreach( (array) $needles as $needle )
208
		{
209
			$needle = (string) $needle;
210
211
			if( $needle !== '' && strncmp( $str, $needle, strlen( $needle ) ) === 0 ) {
212
				return true;
213
			}
214
		}
215
216
		return false;
217
	}
218
219
220
	/**
221
	 * Returns a date formatted according to the given format string.
222
	 *
223
	 * @param string $format DateTime format string but with format characters preceded by "%"
224
	 * @return string String with date/time according to the format string
225
	 */
226
	public static function strtime( string $format ) : string
227
	{
228
		return trim( date_create()->format( str_replace( '\\%\\', '', '\\' . join( '\\', str_split( $format ) ) ) ) );
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

228
		return trim( date_create()->format( str_replace( '\\%\\', '', '\\' . join( '\\', /** @scrutinizer ignore-type */ str_split( $format ) ) ) ) );
Loading history...
229
	}
230
231
232
	/**
233
	 * Generates a unique ID string suitable as global identifier
234
	 *
235
	 * The ID is similar to an UUID and is as unique as an UUID but it's human
236
	 * readable string is only 20 bytes long. Additionally, the unique ID is
237
	 * optimized for being used as primary key in databases.
238
	 *
239
	 * @return string Global unique ID of 20 bytes length
240
	 */
241
	public static function uid() : string
242
	{
243
		if( self::$node === null )
244
		{
245
			try {
246
				self::$node = random_bytes( 6 );
247
			} catch( \Throwable $t ) {
248
				if( function_exists( 'openssl_random_pseudo_bytes' ) ) {
249
					self::$node = openssl_random_pseudo_bytes( 6 );
250
				} else {
251
					self::$node = pack( 'n*', rand( 0, 0xffff ), rand( 0, 0xffff ), rand( 0, 0xffff ) );
252
				}
253
			}
254
		}
255
256
		$t = gettimeofday();
257
		$sec = $t['sec'];
258
		$usec = $t['usec'];
259
260
		self::$seq = self::$seq + 1 & 0xfff; // 20 bits for sequence (1 to 4,095), wraps around
261
262
		$hsec = ( $sec & 0xff00000000 ) >> 32; // 5th byte from seconds till 1970-01-01T00:00:00 (on 64 bit platforms)
263
		$lsec = $sec & 0xffffffff; // Lowest 32 bits from seconds till 1970-01-01T00:00:00
264
		$husec = ( $usec & 0xffff0 ) >> 4; // Highest 16 bits from micro seconds (total 20 bits)
265
		$mix = ( $usec & 0xf ) << 4 | ( self::$seq & 0xf00 ) >> 8; // Lowest 4 bits (usec) and highest 4 bits (seq)
266
		$lseq = self::$seq & 0xff; // Lowest 16 bits from sequence
267
268
		// 5 bytes seconds, 2 byte usec, 1 byte usec+seq, 1 byte seq, 6 bytes node
269
		$uid = base64_encode( pack( 'CNnCC', $hsec, $lsec, $husec, $mix, $lseq ) . self::$node );
270
271
		return str_replace( ['+', '/'], ['-', '_'], $uid ); // URL safety
272
	}
273
}
274