Passed
Push — main ( e55150...6ce6d7 )
by Will
02:49
created

devices   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 409
Duplicated Lines 0 %

Test Coverage

Coverage 99.73%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 39
eloc 315
c 2
b 0
f 0
dl 0
loc 409
ccs 369
cts 370
cp 0.9973
rs 9.28

3 Methods

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

328
		$parts = \explode('Build/', /** @scrutinizer ignore-type */ \str_ireplace('build/', 'Build/', $value), 2);
Loading history...
329 29
		$parts[0] = \trim($parts[0]);
330 29
		$build = $parts[1] ?? null;
331 29
		$vendors = [
332 29
			'Samsung' => 'Samsung',
333 29
			'OnePlus' => 'OnePlus',
334 29
			'CPH' =>'OnePlus',
335 29
			'KB' => 'OnePlus',
336 29
			'Pixel' => 'Google',
337 29
			'SM-' => 'Samsung',
338 29
			'LM-' => 'LG',
339 29
			'LG' => 'LG',
340 29
			'RealMe' => 'RealMe',
341 29
			'RMX' => 'RealMe',
342 29
			'HTC' => 'HTC',
343 29
			'Nexus' => 'Google',
344 29
			'Redmi' => 'Redmi', // must be above 'MI '
345 29
			'MI ' => 'Xiaomi',
346 29
			'HM ' => 'Xiaomi',
347 29
			'Xiaomi' => 'Xiaomi',
348 29
			'Huawei' => 'Huawei',
349 29
			'Honor' => 'Honor',
350 29
			'Motorola' => 'Motorola',
351 29
			'moto' => 'Motorola',
352 29
			'Intel' => 'Intel',
353 29
			'SonyEricsson' => 'Sony Ericsson',
354 29
			'Tecno' => 'Tecno',
355 29
			'Vivo' => 'Vivo',
356 29
			'Oppo' => 'Oppo',
357 29
			'Asus' => 'Asus',
358 29
			'Acer' => 'Acer',
359 29
			'Alcatel' => 'Alcatel',
360 29
			'Infinix' => 'Infinix',
361 29
			'Poco' => 'Poco',
362 29
			'Cubot' => 'Cubot',
363 29
			'Nokia' => 'Nokia'
364 29
		];
365
366
		// find vendor
367 29
		$vendor = null;
368 29
		foreach ($vendors AS $key => $item) {
369 29
			if (($pos = \mb_stripos($value, $key)) !== false) {
370 21
				$vendor = self::getVendor($item);
371
372
				// remove vendor name
373 21
				if ($pos === 0 && ($key === $item || $key === 'SonyEricsson')) {
374 13
					$parts[0] = \trim(\mb_substr($parts[0], \mb_strlen($key)), ' -_/');
375
				}
376 21
				break;
377
			}
378
		}
379 29
		$model = \explode(' ', $parts[0], 2);
380 29
		$device = $model[0] !== '' && \ctype_alpha($model[0]) ? \ucfirst($model[0]) : null; // device name if only letters
381 29
		$model = $device === null ? \implode(' ', $model) : ($model[1] ?? null); // reconstruct remainder of device name
382
383
		// remove everything after a slash
384 29
		if ($build === null && \str_contains($model ?? '', '/')) {
385 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

385
			$model = \mb_strstr(/** @scrutinizer ignore-type */ $model, '/', true);
Loading history...
386
		}
387
388
		// special case for SMART TV
389 29
		if (\strcasecmp($device.$model, 'smarttv') === 0) {
390 1
			$device = 'Smart TV';
391 1
			$model = null;
392
		}
393
		// var_dump($value, $parts, $device, $model);
394 29
		return [
395 29
			'vendor' => $vendor,
396 29
			'device' => $device,
397 29
			'model' => $model ? \ucwords($model) : null,
398 29
			'build' => $build
399 29
		];
400
	}
401
402 21
	public static function getVendor(string $value) : string {
403 21
		$map = [
404 21
			'oneplus' => 'OnePlus',
405 21
			'lg' => 'LG',
406 21
			'lge' => 'LG',
407 21
			'realme' => 'RealMe',
408 21
			'htc' => 'HTC',
409 21
			'sonyericsson' => 'Sony Ericsson',
410 21
			'tcl' => 'TCL',
411 21
			'zte' => 'ZTE',
412 21
			'hmd' => 'HMD',
413 21
			'lt' => 'LT'
414 21
		];
415 21
		$value = \mb_strtolower($value);
416 21
		return $map[$value] ?? \mb_convert_case($value, MB_CASE_TITLE);
417
	}
418
}