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

apps   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 420
Duplicated Lines 0 %

Test Coverage

Coverage 99.74%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 321
c 1
b 0
f 0
dl 0
loc 420
ccs 380
cts 381
cp 0.9974
rs 5.5199
wmc 56

2 Methods

Rating   Name   Duplication   Size   Complexity  
A getApp() 0 36 3
F get() 0 374 53

How to fix   Complexity   

Complex Class

Complex classes like apps often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use apps, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types = 1);
3
namespace hexydec\agentzero;
4
5
class apps {
6
7
	/**
8
	 * Generates a configuration array for matching apps
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 52
	public static function get() : array {
13 1
		$fn = [
14 1
			'appslash' => function (string $value, int $i, array $tokens, string $match) : array {
15 52
				if (\mb_stripos($value, 'AppleWebKit') === false && !\str_contains($value, '://')) {
16 27
					$parts = \explode('/', $value, 4);
17 27
					$offset = isset($parts[2]) ? 1 : 0;
18 27
					$app = \str_replace('GooglePlayStore ', '', $parts[0 + $offset]);
19 27
					if (\mb_stripos($app, \rtrim($match, '/')) !== false) { // check the match is before the slash
20 27
						return [
21 27
							'app' => self::getApp($app),
22 27
							'appname' => \trim($app, '[]'),
23 27
							'appversion' => $parts[1 + $offset] ?? null
24 27
						];
25
					}
26
				}
27 41
				return [];
28 1
			},
29 1
		];
30 3
		return [
31 3
			'com.google.' => new props('start', function (string $value) : array {
32 1
				$parts = \explode('/', $value, 3);
33 1
				return [
34 1
					'type' => 'human',
35 1
					'app' => self::getApp($parts[0]),
36 1
					'appname' => $parts[0],
37 1
					'appversion' => $parts[1] ?? null
38 1
				];
39 3
			}),
40 3
			'OcIdWebView' => new props('exact', function (string $value) : array {
41 1
				$data = [
42 1
					'app' => 'Google App Web View',
43 1
					'appname' => $value
44 1
				];
45 1
				return $data;
46 3
			}),
47 3
			'Instagram' => new props('any', function (string $value, int $i, array $tokens) : array {
48 8
				$parts = \explode(' ', $value, 4);
49 8
				$data = [
50 8
					'type' => 'human',
51 8
					'app' => 'Instagram',
52 8
					'appname' => 'Instagram',
53 8
					'appversion' => $parts[1] ?? null,
54 8
					'platform' => empty($parts[2]) ? null : platforms::getPlatform($parts[2])
55 8
				];
56 8
				foreach (\array_slice($tokens, $i + 1) AS $key => $item) {
57 8
					$ipad = null;
58 8
					if (($ipados = \str_starts_with($item, 'iPadOS')) || \str_starts_with($item, 'iOS ')) {
59 2
						$data['kernel'] = 'Linux';
60 2
						$data['platform'] = 'iOS';
61 2
						$data['platformversion'] = \str_replace('_', '.', \mb_substr($item, $ipados ? 7 : 4));
62 2
						$data['density'] = \floatval(\mb_substr($item, 6));
63 8
					} elseif (($iphone = \str_starts_with($item, 'iPhone')) || ($ipad = \str_starts_with($item, 'iPad')) || \str_starts_with($item, 'iPod')) {
64 2
						$data['vendor'] = 'Apple';
65 2
						$data['category'] = $ipad ? 'tablet' : 'mobile';
66 2
						$data['device'] = $iphone ? 'iPhone' : ($ipad ? 'iPad' : 'iPod');
67 2
						$data['model'] = \str_replace(',', '.', \mb_substr($item, $iphone ? 6 : 4));
68 2
						$data['architecture'] = 'Arm';
69 2
						$data['bits'] = 64;
70 8
					} elseif (\str_starts_with($item, 'scale=')) {
71 2
						$data['density'] = \floatval(\mb_substr($item, 6));
72 8
					} elseif (\str_ends_with($item, 'dpi')) {
73 7
						$data['dpi'] = \intval(\mb_substr($item, 0, -3));
74 8
					} elseif (\str_contains($item, 'x') && \strspn($item, '0123456789x') === \strlen($item)) {
75 8
						list($data['width'], $data['height']) = \array_map('intval', \explode('x', $item, 2));
76
77
						// get device when the UA string starts with "Instagram"
78 8
						if ($i === 0 && !isset($data['vendor'])) {
79 1
							$device = [
80 1
								\trim(\mb_strstr($tokens[$key + 2] ?? '', '/') ?: $tokens[$key + 2] ?? '', '/ '), // sometimes the vendor name has a / with the correct name after
81 1
								$tokens[$key + 3] ?? null
82 1
							];
83
84
							// remove duplicated name
85 1
							if ($device[1] !== null && \mb_stripos($device[1], $device[0]) === 0) {
86 1
								unset($device[0]);
87
							}
88 1
							$data = \array_merge($data, devices::getDevice(\implode(' ', \array_filter($device))));
89 1
							break;
90
						}
91
					}
92
				}
93 8
				return $data;
94 3
			}),
95 3
			'imoAndroid/' => new props('start', fn (string $value, int $i, array $tokens) : array => \array_merge(
96 3
				isset($tokens[$i + 3]) ? devices::getDevice($tokens[$i + 3]) : [],
97 3
				[
98 3
					'app' => 'imo',
99 3
					'appname' => 'imoAndroid',
100 3
					'appversion' => ($ver = \mb_strstr($value, '/')) === false ? $value : \ltrim($ver, '/'),
101 3
					'platform' => 'Android',
102 3
					'platformversion' => $tokens[$i + 1] ?? null,
103 3
					'cpu' => $tokens[$i + 11] ?? null,
104 3
					'vendor' => isset($tokens[$i + 4]) ? devices::getVendor($tokens[$i + 4]) : null
105 3
				]
106 3
			)),
107 3
			'GSA/' => new props('any', $fn['appslash']),
108 3
			'DuckDuckGo/' => new props('start', $fn['appslash']),
109 3
			'FlipboardProxy/' => new props('start', $fn['appslash']),
110 3
			'Emacs/' => new props('start', $fn['appslash']),
111 3
			'AndroidDownloadManager/' => new props('start', $fn['appslash']),
112 3
			'Zoom ' => new props('start', fn (string $value) : array => [
113 2
				'app' => 'Zoom',
114 2
				'appname' => 'Zoom',
115 2
				'appversion' => \mb_substr($value, 5)
116 2
			]),
117 3
			'Reddit/' => new props('start', function (string $value, int $i, array $tokens) : array {
118 1
				$os = !empty($tokens[$i+2]) ? \explode('/', $tokens[$i+2], 2) : null;
119 1
				$parts = !empty($os[1]) ? \explode(' ', $os[1], 3) : null;
120 1
				return [
121 1
					'type' => 'human',
122 1
					'app' => 'Reddit',
123 1
					'appname' => 'Reddit',
124 1
					'appversion' => $value === 'Reddit/Version' ? (\mb_strstr($tokens[$i+1], '/', true) ?: null) : \mb_substr($value, 7),
125 1
					'platform' => $parts[0] ?? null,
126 1
					'platformversion' => $tokens[$i+3] ?? null
127 1
				];
128 3
			}),
129 3
			'Pinterest for ' => new props('start', fn (string $value) : array => [
130 1
				'type' => 'human',
131 1
				'app' => 'Pinterest',
132 1
				'appname' => 'Pinterest',
133 1
				'appversion' => \explode('/', $value, 3)[1],
134 1
				'platform' => \mb_substr($value, 14, \mb_strpos($value, '/') - 14)
135 1
			]),
136 3
			'OculusBrowser/' => new props('start', $fn['appslash']),
137 3
			'BaiduBrowser/' => new props('start', $fn['appslash']),
138 3
			'bdhonorbrowser/' => new props('start', $fn['appslash']),
139 3
			'YaBrowser/' => new props('start', $fn['appslash']),
140 3
			'choqok/' => new props('start', $fn['appslash']),
141 3
			'Quora ' => new props('start', fn (string $value) : array => [
142 1
				'type' => 'human',
143 1
				'app' => 'Quora',
144 1
				'appname' => 'Quora',
145 1
				'appversion' => \explode(' ', $value, 3)[1]
146 1
			]),
147 3
			'AmazonKidsBrowser/' => new props('start', $fn['appslash']),
148 3
			'whisper/' => new props('start', $fn['appslash']),
149 3
			'Teams/' => new props('start', $fn['appslash']),
150 3
			'Viber/' => new props('start', $fn['appslash']),
151 3
			'MXPlayer/' => new props('start', fn (string $value) : array => [
152 1
				'app' => 'MXPlayer',
153 1
				'appname' => 'MXPlayer',
154 1
				'appversion' => \explode('/', $value, 3)[1] ?: null
155 1
			]),
156 3
			'Todoist-Android/' => new props('start', fn (string $value) : array => [
157 1
				'type' => 'human',
158 1
				'platform' => 'Android',
159 1
				'app' => 'Todoist',
160 1
				'appname' => 'Todoist-Android',
161 1
				'appversion' => \explode('/', $value, 3)[1] ?: null
162 1
			]),
163 3
			'Trello for Android/' => new props('start', fn (string $value) : array => [
164 1
				'type' => 'human',
165 1
				'platform' => 'Android',
166 1
				'app' => 'Trello',
167 1
				'appname' => 'Trello for Android',
168 1
				'appversion' => \explode('/', $value, 3)[1] ?: null
169 1
			]),
170 3
			'iPad PurpleMashApp' => new props('start', fn (string $value) : array => [
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

170
			'iPad PurpleMashApp' => new props('start', fn (/** @scrutinizer ignore-unused */ string $value) : array => [

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
171 1
				'type' => 'human',
172 1
				'platform' => 'iOS',
173 1
				'vendor' => 'Apple',
174 1
				'device' => 'iPad',
175 1
				'app' => 'Purple Mash',
176 1
				'appname' => 'PurpleMashApp'
177 1
			]),
178 3
			'Instapaper for Android ' => new props('start', fn (string $value) : array => [
179 1
				'type' => 'human',
180 1
				'platform' => 'Android',
181 1
				'app' => 'Instapaper',
182 1
				'appname' => 'Instapaper',
183 1
				'appversion' => \mb_substr($value, \mb_strrpos($value, ' ') + 1)
184 1
			]),
185 3
			'Instapaper' => new props('start', fn (string $value, int $i, array $tokens, string $match) => \array_merge([
186 3
				'type' => 'human'
187 3
			], $fn['appslash']($value, $i, $tokens, $match))),
188 3
			'Player/LG' => new props('start', function (string $value, int $i, array $tokens) : ?array {
189 1
				if (\str_starts_with($tokens[$i+1], 'Player ')) {
190 1
					$parts = \explode(' ', $tokens[$i+1]);
191 1
					$device = $i === 1 ? \explode('/', $tokens[0]) : null;
192 1
					return [
193 1
						'type' => 'human',
194 1
						'vendor' => 'LG',
195 1
						'device' => $device[0] ?? null,
196 1
						'model' => $device[1] ?? null,
197 1
						'app' => 'LG Player',
198 1
						'appname' => 'LG Player',
199 1
						'appversion' => $parts[1] ?? null,
200 1
						'platform' => $parts[3] ?? null,
201 1
						'platformversion' => $parts[4] ?? null
202 1
					];
203
				}
204
				return null;
205 3
			}),
206 3
			'RobloxApp/' => new props('any', $fn['appslash']),
207 3
			'nu.nl/' => new props('any', fn (string $value) : array => [
208 1
				'app' => 'nu.nl',
209 1
				'appname' => 'nu.nl',
210 1
				'appversion' => \mb_substr($value, 6)
211 1
			]),
212 3
			'Google Web Preview' => new props('start', $fn['appslash']),
213 3
			'MicroMessenger/' => new props('start', $fn['appslash']),
214 3
			'MicroMessenger Weixin QQ' => new props('start', fn () : array => [
215 2
				'app' => 'WeChat',
216 2
				'appname' => 'MicroMessenger'
217 2
			]),
218 3
			'weibo' => new props('any', function (string $value) : array {
219 1
				$data = [
220 1
					'app' => 'Weibo',
221 1
					'appname' => 'Weibo'
222 1
				];
223 1
				$parts = \explode('_', $value);
224 1
				foreach ($parts AS $i => $item) {
225 1
					if (\mb_stripos($item, 'Weibo') !== false) {
226 1
						$data['appname'] = $item;
227 1
						$data['appversion'] = $parts[$i + (\strspn($parts[$i + 1] ?? '', '0123456789', 0, 1) === 1 ? 1 : 2)] ?? null;
228 1
						break;
229
					}
230
				}
231 1
				return $data;
232 3
			}),
233
234
			// special parser for Facebook app because it is completely different to any other
235 3
			'FBAN/' => new props('any', function (string $value) : array {
236 4
				$map = [
237 4
					'FBAN/MessengerLiteForiOS' => [
238 4
						'type' => 'human',
239 4
						'app' => 'Facebook Messenger',
240 4
						'platform' => 'iOS'
241 4
					],
242 4
					'FBAN/FB4A' => [
243 4
						'type' => 'human',
244 4
						'app' => 'Facebook',
245 4
						'platform' => 'Android'
246 4
					],
247 4
					'FBAN/FBIOS' => [
248 4
						'type' => 'human',
249 4
						'app' => 'Facebook',
250 4
						'platform' => 'iOS'
251 4
					],
252 4
					'FBAN/FB4FireTV' => [
253 4
						'type' => 'human',
254 4
						'category' => 'tv',
255 4
						'app' => 'Facebook',
256 4
						'platform' => 'Android'
257 4
					],
258 4
					'FBAN/MessengerDesktop' => [
259 4
						'type' => 'human',
260 4
						'category' => 'desktop',
261 4
						'app' => 'Facebook Messenger'
262 4
					],
263 4
					'FacebookCanvasDesktop FBAN/GamesWindowsDesktopApp' => [
264 4
						'type' => 'human',
265 4
						'platform' => 'Windows',
266 4
						'category' => 'desktop',
267 4
						'app' => 'Facebook Gamesroom'
268 4
					]
269 4
				];
270 4
				return \array_merge([
271 4
					'type' => 'human',
272 4
					'app' => 'Facebook',
273 4
					'appname' => \explode('/', $value, 2)[1]
274 4
				], $map[$value] ?? []);
275 3
			}),
276 3
			'FB_IAB/' => new props('start', fn (string $value) : array => [
277 7
				'app' => 'Facebook',
278 7
				'appname' => \mb_substr($value, 7)
279 7
			]),
280 3
			'FBAV/' => new props('start', fn (string $value) : array => [
281 9
				'appversion' => \mb_substr($value, 5)
282 9
			]),
283 3
			'FBMF/' => new props('start', fn (string $value) : array => [
284 2
				'vendor' => devices::getVendor(\mb_substr($value, 5))
285 2
			]),
286 3
			'FBDV/' => new props('start', fn (string $value) : array => devices::getDevice(\mb_substr($value, 5))),
287 3
			'FBMD/' => new props('start', fn (string $value) : array => [
288 3
				'model' => \mb_substr($value, 5)
289 3
			]),
290 3
			'FBDM/' => new props('start', function (string $value) : array {
291 1
				$data = [];
292 1
				foreach (\explode(',', \trim(\mb_substr($value, 5), '{}')) AS $item) {
293 1
					$parts = \explode('=', $item);
294 1
					if (!empty($parts[1])) {
295 1
						if (\is_numeric($parts[1])) {
296 1
							$parts[1] = \str_contains($parts[1], '.') ? \floatval($parts[1]) : \intval($parts[1]);
297
						}
298 1
						$data[$parts[0]] = $parts[1];
299
					}
300
				}
301 1
				return $data;
302 3
			}),
303 3
			'width=' => new props('start', fn (string $value) : array => [
304 2
				'width' => \intval(\mb_substr($value, 6))
305 2
			]),
306 3
			'height=' => new props('start', fn (string $value) : array => [
307 2
				'height' => \intval(\mb_substr($value, 7))
308 2
			]),
309 3
			'dpi=' => new props('start', fn (string $value) : array => [
310 1
				'dpi' => \mb_substr($value, 4)
311 1
			]),
312 3
			'FBSN/' => new props('start', fn (string $value) : array => [
313 3
				'platform' => \mb_substr($value, 5)
314 3
			]),
315 3
			'FBSV' => new props('start', fn (string $value) : array => [
316 3
				'platformversion' => \mb_substr($value, 5)
317 3
			]),
318 3
			'isDarkMode/' => new props('start', function (string $value) : array {
319 1
				$mode = \mb_substr($value, 11);
320 1
				return [
321 1
					'darkmode' => \in_array($mode, ['0', '1'], true) ? \boolval($mode) : null
322 1
				];
323 3
			}),
324 3
			'AppTheme/' => new props('start', fn (string $value) : array => [
325 1
				'darkmode' => \mb_substr($value, 9) === 'dark'
326 1
			]),
327 3
			'NetType/' => new props('start', fn (string $value) : array => [
328 5
				'nettype' => \mb_convert_case(\mb_substr($value, 8), MB_CASE_UPPER)
329 5
			]),
330 3
			'Microsoft Office' => new props('start', function (string $value, int $i, array $tokens) : array {
331 1
				$data = [
332 1
					'type' => 'human'
333 1
				];
334 1
				if (\str_contains($value, '/')) {
335 1
					foreach (\array_slice($tokens, $i + 1) AS $item) {
336 1
						if (\str_starts_with($item, 'Microsoft ')) {
337 1
							$parts = \explode(' ', $item);
338 1
							$data['app'] = $parts[0].' '.$parts[1];
339 1
							$data['appname'] = $parts[0].' '.$parts[1];
340 1
							if (isset($parts[2])) {
341 1
								$data['appversion'] = $parts[2];
342
							}
343 1
							break;
344
						}
345
					}
346 1
					if (!isset($data['app'])) {
347 1
						$parts = \explode('/', $value, 2);
348 1
						$data['app'] = $parts[0];
349 1
						$data['appname'] = $parts[0];
350 1
						if (!isset($data['appversion'])) {
351 1
							$data['appversion'] = $parts[1];
352
						}
353
					}
354
				} else {
355 1
					$parts = \explode(' ', $value);
356 1
					$data['app'] = \rtrim($parts[0].' '.($parts[1] ?? '').' '.($parts[2] ?? ''));
357 1
					$data['appname'] = $value;
358 1
					$data['appversion'] = $parts[3] ?? null;
359
				}
360 1
				return $data;
361 3
			}),
362
363
			// TikTok
364 3
			'AppName/' => new props('start', fn(string $value) : array => [
365 3
				'app' => $value === 'AppName/musical_ly' ? 'TikTok' : \mb_substr($value, 8),
366 3
				'appname' => \mb_substr($value, 8)
367 3
			]),
368 3
			'app_version/' => new props('start', fn(string $value) : array => [
369 3
				'appversion' => \mb_substr($value, 12)
370 3
			]),
371 3
			'AppVersion/' => new props('any', fn(string $value) : array => [
372 2
				'appversion' => \explode('/', $value, 2)[1]
373 2
			]),
374 3
			'musical_ly' => new props('start', fn(string $value) : array => [
375 2
				'app' => 'TikTok',
376 2
				'appname' => 'musical_ly',
377 2
				'appversion' => \str_replace('_', '.', \mb_substr(\explode(' ', $value, 2)[0], 11))
378 2
			]),
379 3
			'Android App' => new props('any', [
380 3
				'type' => 'human',
381 3
				'platform' => 'Android'
382 3
			]),
383
				
384
			// generic
385 3
			'App' => new props('any', $fn['appslash'])
386 3
		];
387
	}
388
389 27
	public static function getApp(string $value) : string {
390 27
		$map = [
391 27
			'YaApp_iOS' => 'Yandex',
392 27
			'YaApp_Android' => 'Yandex',
393 27
			'YaSearchApp' => 'Yandex',
394 27
			'YaBrowser' => 'Yandex',
395 27
			'LinkedInApp' => 'LinkedIn',
396 27
			'[LinkedInApp]' => 'LinkedIn',
397 27
			'GoogleApp' => 'Google',
398 27
			'com.zhiliaoapp.musically' => 'TikTok',
399 27
			'com.google.android.apps.searchlite' => 'Google',
400 27
			'com.google.android.googlequicksearchbox' => 'Google',
401 27
			'com.google.android.gms' => 'Google',
402 27
			'com.google.GoogleMobile' => 'Google',
403 27
			'com.google.Maps' => 'Google Maps',
404 27
			'com.google.photos' => 'Google Photos',
405 27
			'com.google.ios.youtube' => 'YouTube',
406 27
			'com.google.android.youtube' => 'YouTube',
407 27
			'AlohaBrowserApp' => 'Aloha',
408 27
			'OculusBrowser' => 'Oculus Browser',
409 27
			'AndroidDownloadManager' => 'Android Download Manager',
410 27
			'imoAndroid' => 'imo',
411 27
			'MicroMessenger' => 'WeChat',
412 27
			'GSA' => 'Google',
413 27
			'baiduboxapp' => 'Baidu',
414 27
			'lite baiduboxapp' => 'Baidu',
415 27
			'baidubrowser' => 'Baidu',
416 27
			'bdhonorbrowser' => 'Baidu',
417 27
			'RobloxApp' => 'Roblox'
418 27
		];
419 27
		if (isset($map[$value])) {
420 13
			return $map[$value];
421 17
		} elseif (($pos = \mb_strrpos($value, '.')) !== false) {
422 3
			return \mb_substr($value, $pos + 1);
423
		}
424 15
		return $value;
425
	}
426
}