Passed
Push — main ( d3029f...a35a1e )
by Will
14:00
created

devices::get()   F

Complexity

Conditions 32
Paths 1

Size

Total Lines 361
Code Lines 296

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 348
CRAP Score 32

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 32
eloc 296
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 361
ccs 348
cts 349
cp 0.9971
crap 32
rs 3.3333

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types = 1);
3
namespace hexydec\agentzero;
4
5
class devices {
6
7
	/**
8
	 * Generates a configuration array for matching devices
9
	 * 
10
	 * @return array<string,props> An array with keys representing the string to match, and values a props object defining how to generate the match and which properties to set
11
	 */
12 90
	public static function get() : array {
13 3
		$fn = [
14 3
			'ios' => function (string $value, int $i, array $tokens) : array {
15 19
				$version = null;
16 19
				$model = null;
17
18
				// device name with slash like iPhone/16.5
19 19
				$parts = \explode('/', $value, 3);
20 19
				if (isset($parts[1])) {
21 1
					$value = $parts[0];
22 1
					$version = $parts[1];
23
				}
24
25
				// look at other tokens to gather info
26 19
				foreach ($tokens AS $item) {
27 19
					if (\str_starts_with($item, 'Mobile/')) {
28 19
						$model = \mb_substr($item, 7);
29 19
					} elseif (\str_starts_with($item, 'CPU iPhone OS ')) {
30 18
						$version = \str_replace('_', '.', \mb_substr($item, 14, \mb_strpos($item, ' ', 14) - 14));
31 19
					} elseif (\str_starts_with($item, 'CPU OS ')) {
32 2
						$version = \str_replace('_', '.', \mb_substr($item, 7, \mb_strpos($item, ' ', 7) - 7));
33
					}
34
				}
35 19
				return [
36 19
					'type' => 'human',
37 19
					'category' => $value === 'iPad' ? 'tablet' : 'mobile',
38 19
					'architecture' => 'Arm',
39 19
					'bits' => $value === 'iPod' ? 32 : 64,
40 19
					'kernel' => 'Linux',
41 19
					'platform' => 'iOS',
42 19
					'platformversion' => $version,
43 19
					'vendor' => 'Apple',
44 19
					'device' => \mb_stripos($value, 'iPhone') === 0 ? 'iPhone' : $value,
45 19
					'model' => $model
46 19
				];
47 3
			},
48 3
			'xbox' => fn (string $value) : array => [
49 3
				'type' => 'human',
50 3
				'category' => 'console',
51 3
				'vendor' => 'Microsoft',
52 3
				'device' => 'Xbox',
53 3
				'model' => ($model = \mb_substr($value, 5)) === '' ? null : $model
54 3
			],
55 3
			'playstation' => function (string $value) : array {
56 1
				$parts = \explode(' ', $value);
57 1
				if (\str_contains($parts[1], '/')) {
58 1
					list($parts[1], $parts[2]) = \explode('/', $parts[1]);
59
				}
60 1
				$platform = [
61 1
					'4' => 'Orbis OS',
62 1
					'5' => 'FreeBSD'
63 1
				];
64 1
				return [
65 1
					'device' => 'PlayStation',
66 1
					'model' => $parts[1] ?? null,
67 1
					'kernel' => 'Linux',
68 1
					'platform' => $platform[$parts[1]] ?? null,
69 1
					'platformversion' => $parts[2] ?? null,
70 1
					'type' => 'human',
71 1
					'category' => 'console',
72 1
					'vendor' => 'Sony',
73 1
					'processor' => 'AMD',
74 1
					'architecture' => 'x86',
75 1
					'bits' => 64
76 1
				];
77 3
			},
78 3
			'firetablet' => function (string $value) : ?array {
79 3
				$model = \explode(' ', $value)[0];
80 3
				if (\ctype_alpha($model) && \mb_strlen($model) <= 7) {
81 3
					return [
82 3
						'type' => 'human',
83 3
						'category' => 'tablet',
84 3
						'vendor' => 'Amazon',
85 3
						'device' => 'Fire Tablet',
86 3
						'model' => $model
87 3
					];
88
				}
89
				return null;
90 3
			}
91 3
		];
92 32
		return [
93 32
			'iPhone' => new props('start', $fn['ios']),
94 32
			'iPad' => new props('exact', $fn['ios']),
95 32
			'iPod' => new props('exact', $fn['ios']),
96 32
			'iPod touch' => new props('exact', $fn['ios']),
97 32
			'Macintosh' => new props('exact', [
98 32
				'vendor' => 'Apple',
99 32
				'device' => 'Macintosh'
100 32
			]),
101 32
			'Quest' => new props('start', fn (string $value) : array => [
102 4
				'vendor' => 'Oculus',
103 4
				'device' => 'Quest',
104 4
				'model' => ($model = \mb_substr($value, 6)) === '' ? null : $model,
105 4
				'type' => 'human',
106 4
				'category' => 'vr'
107 4
			]),
108 32
			'Pacific' => new props('start', [
109 32
				'vendor' => 'Oculus',
110 32
				'device' => 'Go',
111 32
				'type' => 'human',
112 32
				'category' => 'vr'
113 32
			]),
114 32
			'Nintendo' => new props('start', fn (string $value) : array => [
115 4
				'type' => 'human',
116 4
				'category' => 'console',
117 4
				'vendor' => 'Nintendo',
118 4
				'device' => $value === 'Nintendo WiiU' ? 'Wii U' : \mb_substr($value, 9),
119 4
				'architecture' => \str_ends_with($value, 'U') ? 'PowerPC' : null
120 4
			]),
121 32
			'Xbox Series S' => new props('exact', $fn['xbox']),
122 32
			'Xbox Series X' => new props('exact', $fn['xbox']),
123 32
			'Xbox One' => new props('exact', $fn['xbox']),
124 32
			'Xbox 360' => new props('exact', $fn['xbox']),
125 32
			'Xbox' => new props('exact', $fn['xbox']),
126 32
			'Playstation 3' => new props('start', $fn['playstation']),
127 32
			'Playstation 4' => new props('start', $fn['playstation']),
128 32
			'Playstation 5' => new props('start', $fn['playstation']),
129 32
			'Playstation Vita' => new props('start', fn (string $value) : array => [
130 2
				'type' => 'human',
131 2
				'category' => 'console',
132 2
				'vendor' => 'Sony',
133 2
				'device' => 'PlayStation',
134 2
				'model' => 'Vita',
135 2
				'architecture' => 'Arm',
136 2
				'processor' => 'MediaTek',
137 2
				'cpu' => 'Cortex-A9 MPCore',
138 2
				'bits' => 32,
139 2
				'width' => 960,
140 2
				'height' => 544,
141 2
				'dpi' => 220,
142 2
				'ram' => 512,
143 2
				'kernel' => 'Linux',
144 2
				'platform' => 'PlayStation Vita System Software',
145 2
				'platformversion' => \mb_substr($value, 17)
146 2
			]),
147 32
			'Playstation Portable' => new props('start', fn (string $value, int $i, array $tokens) : array => [
148 2
				'type' => 'human',
149 2
				'category' => 'console',
150 2
				'vendor' => 'Sony',
151 2
				'device' => 'PlayStation',
152 2
				'model' => 'PSP',
153 2
				'architecture' => 'Arm',
154 2
				'cpu' => 'MIPS R4000',
155 2
				'cpuclock' => 333,
156 2
				'bits' => 64,
157 2
				'width' => 480,
158 2
				'height' => 272,
159 2
				'ram' => 64,
160 2
				'kernel' => 'Linux',
161 2
				'platform' => 'PlayStation Portable System Software',
162 2
				'platformversion' => $tokens[++$i],
163 2
				'browser' => 'NetFront',
164 2
				'engine' => 'WebKit'
165 2
			]),
166 32
			'SHIELD Android TV' => new props('start', [
167 32
				'type' => 'human',
168 32
				'category' => 'console',
169 32
				'vendor' => 'NVIDIA',
170 32
				'device' => 'Shield'
171 32
			]),
172 32
			'CrKey/' => new props('start', fn (string $value) : array => [
173 3
				'type' => 'human',
174 3
				'category' => 'tv',
175 3
				'app' => 'Chromecast',
176 3
				'appname' => 'CrKey',
177 3
				'appversion' => \explode(',', \mb_substr($value, 6), 2)[0]
178 3
			]),
179 32
			'ChromeBook' => new props('any', [
180 32
				'type' => 'human',
181 32
				'category' => 'desktop'
182 32
			]),
183 32
			'GoogleTV' => new props('exact', [
184 32
				'type' => 'human',
185 32
				'category' => 'tv',
186 32
				'device' => 'GoogleTV'
187 32
			]),
188 32
			'CriKey/' => new props('start', fn (string $value) : array => [
189 1
				'type' => 'human',
190 1
				'category' => 'tv',
191 1
				'device' => 'Chromecast',
192 1
				'vendor' => 'Google',
193 1
				'platformversion' => \mb_substr($value, 7)
194 1
			]),
195 32
			'Apple/' => new props('start', function (string $value) : array {
196 1
				$value = \mb_substr($value, 6);
197 1
				$split = \strcspn($value, '0123456789');
198 1
				$device = \mb_substr($value, 0, $split);
199 1
				return [
200 1
					'type' => 'human',
201 1
					'category' => $device === 'iPad' ? 'tablet' : 'mobile',
202 1
					'vendor' => 'Apple',
203 1
					'device' => $device,
204 1
					'model' => \mb_substr($value, $split) ?: null
205 1
				];
206 32
			}),
207 32
			'hw/iPhone' => new props('start', fn (string $value) : array => [
208 1
				'platform' => 'iOS',
209 1
				'vendor' => 'Apple',
210 1
				'device' => 'iPhone',
211 1
				'model' => \str_replace('_', '.', \mb_substr($value, 9))
212 1
			]),
213 32
			'KF' => new props('start', $fn['firetablet']),
214 32
			'AFT' => new props('start', fn (string $value) : array => [
215 2
				'type' => 'human',
216 2
				'category' => 'tv',
217 2
				'vendor' => 'Amazon',
218 2
				'device' => 'Fire TV',
219 2
				'model' => $value
220 2
			]),
221 32
			'Roku/' => new props('start', fn (string $value, int $i, array $tokens) : array => [
222 2
				'type' => 'human',
223 2
				'category' => 'tv',
224 2
				'kernel' => 'Linux',
225 2
				'platform' => 'Roku OS',
226 2
				'platformversion' => \mb_substr($value, 5),
227 2
				'vendor' => 'Roku',
228 2
				'device' => 'Roku',
229 2
				'build' => $tokens[++$i] ?? null
230 2
			]),
231 32
			'AmigaOneX' => new props('start', fn (string $value) : array => [
232 2
				'type' => 'human',
233 2
				'category' => 'desktop',
234 2
				'vendor' => 'A-Eon Technology',
235 2
				'device' => 'AmigaOne',
236 2
				'model' => \mb_substr($value, 8)
237 2
			]),
238 32
			'googleweblight' => new props('exact', [
239 32
				'proxy' => 'googleweblight'
240 32
			]),
241 32
			'SAMSUNG-' => new props('start', function (string $value) : array {
242 2
				$parts = \explode('/', $value, 2);
243 2
				return [
244 2
					'type' => 'human',
245 2
					'category' => 'mobile',
246 2
					'vendor' => 'Samsung',
247 2
					'model' => \mb_substr($parts[0], 8),
248 2
					'build' => $parts[1] ?? null,
249 2
				];
250 32
			}),
251 12
			'Samsung' => new props('start', fn (string $value) : ?array => \str_starts_with($value, 'SamsungBrowser') ? null : [
252 12
				'vendor' => 'Samsung'
253 12
			]),
254 32
			'SM-' => new props('start', function (string $value) : array {
255 9
				$parts = \explode('.', \explode(' ', $value)[0]);
256 9
				return [
257 9
					'vendor' => 'Samsung',
258 9
					'model' => $parts[0],
259 9
					'build' => $parts[1] ?? null
260 9
				];
261 32
			}),
262 32
			'Acer' => new props('start', [
263 32
				'vendor' => 'Acer'
264 32
			]),
265 32
			'SonyEricsson' => new props('start', function (string $value) : array {
266 1
				$parts = \explode('/', $value, 2);
267 1
				return [
268 1
					'type' => 'human',
269 1
					'category' => 'mobile',
270 1
					'vendor' => 'Sony Ericsson',
271 1
					'model' => \mb_substr($parts[0], 12),
272 1
					'build' => $parts[1] ?? null
273 1
				];
274 32
			}),
275 32
			'LGE' => new props('exact', function (string $value, int $i, array $tokens) : array {
276 1
				$device = $tokens[++$i] ?? null;
277 1
				$platformversion = empty($tokens[++$i]) ? null : \mb_substr(\explode(' ', $tokens[$i])[0], 5);
278 1
				$build = $tokens[++$i] ?? null;
279 1
				return [
280 1
					'type' => 'human',
281 1
					'category' => 'tv',
282 1
					'model' => $device,
283 1
					'build' => $build,
284 1
					'platformversion' => $platformversion,
285 1
					'vendor' => 'LG'
286 1
				];
287 32
			}),
288 32
			'LG-' => new props('start', function (string $value) : array {
289 1
				$parts = \explode('/', \mb_substr($value, 3), 3);
290 1
				return [
291 1
					'type' => 'human',
292 1
					'category' => 'mobile',
293 1
					'vendor' => 'LG',
294 1
					'model' => \explode(' ', $parts[0])[0],
295 1
					'build' => $parts[1] ?? null
296 1
				];
297 32
			}),
298 32
			'NOKIA' => new props('start', fn (string $value) : array => \array_merge(devices::getDevice($value), [
299 32
				'type' => 'human',
300 32
				'category' => 'mobile',
301 32
				'vendor' => 'Nokia',
302 32
			])),
303 32
			'Lumia' => new props('start', fn (string $value) : array => \array_merge(devices::getDevice($value), [
304 32
				'type' => 'human',
305 32
				'category' => 'mobile',
306 32
				'vendor' => 'Nokia'
307 32
			])),
308 32
			'BRAVIA' => new props('start', [
309 32
				'type' => 'human',
310 32
				'category' => 'tv',
311 32
				'vendor' => 'Sony',
312 32
				'device' => 'Bravia'
313 32
			]),
314 32
			'TECNO' => new props('start', fn (string $value) : array =>  [
315 3
				'type' => 'human',
316 3
				'category' => 'mobile',
317 3
				'vendor' => 'Tecno',
318 3
				'model' => \explode(' ', \str_replace('-', ' ', $value), 2)[1] ?? null
319 3
			]),
320 32
			'Xiaomi' => new props('start', function (string $value, int $i, array $tokens) : array {
321 4
				return [
322 4
					'type' => 'human',
323 4
					'vendor' => 'Xiaomi',
324 4
					'device' => \mb_stripos($value, 'Poco') !== false ? 'Poco' : null,
325 4
					'model' => \mb_stripos($value, 'Xiaomi-Mi') !== 0 ? (\explode(' ', $value)[1] ?? null) : null,
326 4
					'platformversion' => \is_numeric($tokens[$i+1] ?? null) ? $tokens[$i+1] : null
327 4
				];
328 32
			}),
329 32
			'ThinkPad' => new props('start', function (string $value, int $i, array $tokens) : array {
330 1
				if (\mb_strpos($tokens[++$i] ?? '', 'Build/') === 0) {
331 1
					$device = \explode('_', \mb_substr($tokens[$i], 6));
332
				}
333 1
				return [
334 1
					'type' => 'human',
335 1
					'vendor' => 'Lenovo',
336 1
					'device' => $device[0] ?? null,
337 1
					'model' => $device[1] ?? null,
338 1
					'build' => $device[2] ?? null
339 1
				];
340 32
			}),
341 32
			'BlackBerry' => new props('start', function (string $value) : array {
342 1
				$parts = \explode('/', $value);
343 1
				return [
344 1
					'type' => 'human',
345 1
					'category' => 'mobile',
346 1
					'vendor' => 'Blackberry',
347 1
					'device' => \mb_substr($parts[0], 10) ?: null,
348 1
					'platform' => 'Blackberry OS',
349 1
					'platformversion' => $parts[1] ?? null
350 1
				];
351 32
			}),
352 32
			'Model/' => new props('start', fn (string $value) : array => [
353 1
				'model' => \mb_substr($value, 6)
354 1
			]),
355 32
			'Build/' => new props('any', fn (string $value) : array => self::getDevice($value)),
356 32
			'x' => new props('any', function (string $value) : ?array {
357 90
				if (\str_contains($value, '@')) {
358 2
					$dpi = \explode('@', $value);
359 2
					$value = $dpi[0];
360
				}
361 90
				$parts = \explode('x', $value);
362 90
				if (!isset($parts[2]) && \is_numeric($parts[0]) && \is_numeric($parts[1]) && !empty($parts[0]) && !empty($parts[1])) {
363 11
					return [
364 11
						'width' => \intval($parts[0]),
365 11
						'height' => \intval($parts[1]),
366 11
						'density' => isset($dpi[1]) ? \floatval(\rtrim($dpi[1], 'x')) : null
367 11
					];
368
				}
369 90
				return null;
370 32
			}),
371 32
			'MB' => new props('end', fn (string $value) : array => [
372 2
				'ram' => \intval(\mb_substr($value, 0, -2))
373 2
			])
374 32
		];
375
	}
376
377
	/**
378
	 * Extracts device information from a token
379
	 * 
380
	 * @param string $value A token expected to contain device information
381
	 * @return array<string,string|null> An array containing the extracted devices information
382
	 */
383 41
	public static function getDevice(string $value) : array {
384 41
		foreach (['Mobile', 'Tablet', 'Safari', 'AppleWebKit', 'Linux', 'rv:'] AS $item) {
385 41
			if (\mb_stripos($value, $item) === 0) {
386 7
				return [];
387
			}
388
		}
389 37
		$parts = \explode('Build/', \str_ireplace('build/', 'Build/', $value), 2);
0 ignored issues
show
Bug introduced by
It seems like str_ireplace('build/', 'Build/', $value) can also be of type array; however, parameter $string of explode() does only seem to accept string, 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

389
		$parts = \explode('Build/', /** @scrutinizer ignore-type */ \str_ireplace('build/', 'Build/', $value), 2);
Loading history...
390 37
		$parts[0] = \trim($parts[0]);
391 37
		$build = $parts[1] ?? null;
392 37
		$vendors = [
393 37
			'Samsung' => 'Samsung',
394 37
			'OnePlus' => 'OnePlus',
395 37
			'CPH' =>'OnePlus',
396 37
			'KB' => 'OnePlus',
397 37
			'Pixel' => 'Google',
398 37
			'SM-' => 'Samsung',
399 37
			'LM-' => 'LG',
400 37
			'LG' => 'LG',
401 37
			'LG-' => 'LG',
402 37
			'RealMe' => 'RealMe',
403 37
			'RMX' => 'RealMe',
404 37
			'HTC' => 'HTC',
405 37
			'Nexus' => 'Google',
406 37
			'Redmi' => 'Redmi', // must be above 'MI '
407 37
			'MI ' => 'Xiaomi',
408 37
			'HM ' => 'Xiaomi',
409 37
			'Xiaomi' => 'Xiaomi',
410 37
			'Huawei' => 'Huawei',
411 37
			'Honor' => 'Honor',
412 37
			'Motorola' => 'Motorola',
413 37
			'moto' => 'Motorola',
414 37
			'Intel' => 'Intel',
415 37
			'SonyEricsson' => 'Sony Ericsson',
416 37
			'Tecno' => 'Tecno',
417 37
			'Vivo' => 'Vivo',
418 37
			'Oppo' => 'Oppo',
419 37
			'Asus' => 'Asus',
420 37
			'Acer' => 'Acer',
421 37
			'Alcatel' => 'Alcatel',
422 37
			'Infinix' => 'Infinix',
423 37
			'Poco' => 'Poco',
424 37
			'Cubot' => 'Cubot',
425 37
			'Nokia' => 'Nokia'
426 37
		];
427
428
		// find vendor
429 37
		$vendor = null;
430 37
		foreach ($vendors AS $key => $item) {
431 37
			if (($pos = \mb_stripos($value, $key)) !== false) {
432 27
				$vendor = self::getVendor($item);
433
434
				// remove vendor name
435 27
				if ($pos === 0 && ($key === $item || $key === 'SonyEricsson')) {
436 15
					$parts[0] = \trim(\mb_substr($parts[0], \mb_strlen($key)), ' -_/');
437
				}
438 27
				break;
439
			}
440
		}
441 37
		$model = \explode(' ', $parts[0], 2);
442 37
		$device = $model[0] !== '' && \ctype_alpha($model[0]) ? \ucfirst($model[0]) : null; // device name if only letters
443 37
		$model = $device === null ? \implode(' ', $model) : ($model[1] ?? null); // reconstruct remainder of device name
444
445
		// remove everything after a slash
446 37
		if ($build === null && \str_contains($model ?? '', '/')) {
447 1
			$model = \mb_strstr($model, '/', true);
0 ignored issues
show
Bug introduced by
It seems like $model can also be of type null; however, parameter $haystack of mb_strstr() does only seem to accept string, 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

447
			$model = \mb_strstr(/** @scrutinizer ignore-type */ $model, '/', true);
Loading history...
448
		}
449
450
		// special case for SMART TV
451 37
		if (\strcasecmp($device.$model, 'smarttv') === 0) {
452 1
			$device = 'Smart TV';
453 1
			$model = null;
454
		}
455
		// var_dump($value, $parts, $device, $model);
456 37
		return [
457 37
			'vendor' => $vendor,
458 37
			'device' => $device,
459 37
			'model' => $model ? \ucwords($model) : null,
460 37
			'build' => $build
461 37
		];
462
	}
463
464 28
	public static function getVendor(string $value) : string {
465 28
		$map = [
466 28
			'oneplus' => 'OnePlus',
467 28
			'lg' => 'LG',
468 28
			'lge' => 'LG',
469 28
			'realme' => 'RealMe',
470 28
			'htc' => 'HTC',
471 28
			'sonyericsson' => 'Sony Ericsson',
472 28
			'tcl' => 'TCL',
473 28
			'zte' => 'ZTE',
474 28
			'hmd' => 'HMD',
475 28
			'lt' => 'LT'
476 28
		];
477 28
		$value = \mb_strtolower($value);
478 28
		return $map[$value] ?? \mb_convert_case($value, MB_CASE_TITLE);
479
	}
480
}