Passed
Push — main ( e95c15...6749c0 )
by Will
03:09
created

devices   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 447
Duplicated Lines 0 %

Test Coverage

Coverage 99.75%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 39
eloc 351
c 2
b 0
f 0
dl 0
loc 447
ccs 407
cts 408
cp 0.9975
rs 9.28

3 Methods

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

363
		$parts = \explode('Build/', /** @scrutinizer ignore-type */ \str_ireplace('build/', 'Build/', $value), 2);
Loading history...
364 29
		$parts[0] = \trim($parts[0]);
365 29
		$build = $parts[1] ?? null;
366 29
		$vendors = [
367 29
			'Samsung' => 'Samsung',
368 29
			'OnePlus' => 'OnePlus',
369 29
			'CPH' =>'OnePlus',
370 29
			'KB' => 'OnePlus',
371 29
			'Pixel' => 'Google',
372 29
			'SM-' => 'Samsung',
373 29
			'LM-' => 'LG',
374 29
			'LG' => 'LG',
375 29
			'RealMe' => 'RealMe',
376 29
			'RMX' => 'RealMe',
377 29
			'HTC' => 'HTC',
378 29
			'Nexus' => 'Google',
379 29
			'Redmi' => 'Redmi', // must be above 'MI '
380 29
			'MI ' => 'Xiaomi',
381 29
			'HM ' => 'Xiaomi',
382 29
			'Xiaomi' => 'Xiaomi',
383 29
			'Huawei' => 'Huawei',
384 29
			'Honor' => 'Honor',
385 29
			'Motorola' => 'Motorola',
386 29
			'moto' => 'Motorola',
387 29
			'Intel' => 'Intel',
388 29
			'SonyEricsson' => 'Sony Ericsson',
389 29
			'Tecno' => 'Tecno',
390 29
			'Vivo' => 'Vivo',
391 29
			'Oppo' => 'Oppo',
392 29
			'Asus' => 'Asus',
393 29
			'Acer' => 'Acer',
394 29
			'Alcatel' => 'Alcatel',
395 29
			'Infinix' => 'Infinix',
396 29
			'Poco' => 'Poco',
397 29
			'Cubot' => 'Cubot',
398 29
			'Nokia' => 'Nokia'
399 29
		];
400
401
		// find vendor
402 29
		$vendor = null;
403 29
		foreach ($vendors AS $key => $item) {
404 29
			if (($pos = \mb_stripos($value, $key)) !== false) {
405 21
				$vendor = self::getVendor($item);
406
407
				// remove vendor name
408 21
				if ($pos === 0 && ($key === $item || $key === 'SonyEricsson')) {
409 13
					$parts[0] = \trim(\mb_substr($parts[0], \mb_strlen($key)), ' -_/');
410
				}
411 21
				break;
412
			}
413
		}
414 29
		$model = \explode(' ', $parts[0], 2);
415 29
		$device = $model[0] !== '' && \ctype_alpha($model[0]) ? \ucfirst($model[0]) : null; // device name if only letters
416 29
		$model = $device === null ? \implode(' ', $model) : ($model[1] ?? null); // reconstruct remainder of device name
417
418
		// remove everything after a slash
419 29
		if ($build === null && \str_contains($model ?? '', '/')) {
420 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

420
			$model = \mb_strstr(/** @scrutinizer ignore-type */ $model, '/', true);
Loading history...
421
		}
422
423
		// special case for SMART TV
424 29
		if (\strcasecmp($device.$model, 'smarttv') === 0) {
425 1
			$device = 'Smart TV';
426 1
			$model = null;
427
		}
428
		// var_dump($value, $parts, $device, $model);
429 29
		return [
430 29
			'vendor' => $vendor,
431 29
			'device' => $device,
432 29
			'model' => $model ? \ucwords($model) : null,
433 29
			'build' => $build
434 29
		];
435
	}
436
437 22
	public static function getVendor(string $value) : string {
438 22
		$map = [
439 22
			'oneplus' => 'OnePlus',
440 22
			'lg' => 'LG',
441 22
			'lge' => 'LG',
442 22
			'realme' => 'RealMe',
443 22
			'htc' => 'HTC',
444 22
			'sonyericsson' => 'Sony Ericsson',
445 22
			'tcl' => 'TCL',
446 22
			'zte' => 'ZTE',
447 22
			'hmd' => 'HMD',
448 22
			'lt' => 'LT'
449 22
		];
450 22
		$value = \mb_strtolower($value);
451 22
		return $map[$value] ?? \mb_convert_case($value, MB_CASE_TITLE);
452
	}
453
}