Passed
Push — main ( 910f4b...bcdb57 )
by Will
02:58
created

apps   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 329
Duplicated Lines 0 %

Test Coverage

Coverage 98.98%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 244
c 1
b 0
f 0
dl 0
loc 329
ccs 291
cts 294
cp 0.9898
rs 8.8
wmc 45

1 Method

Rating   Name   Duplication   Size   Complexity  
F get() 0 322 45

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 47
	public static function get() : array {
13 1
		$fn = [
14 1
			'appslash' => function (string $value, int $i, array $tokens, string $match) : array {
15 47
				if (\mb_stripos($value, 'AppleWebKit') !== 0 && !\str_contains($value, '://')) {
16 21
					$parts = \explode('/', $value, 4);
17 21
					$offset = isset($parts[2]) ? 1 : 0;
18 21
					$map = [
19 21
						'YaApp_iOS' => 'Yandex',
20 21
						'YaApp_Android' => 'Yandex',
21 21
						'YaSearchApp' => 'Yandex',
22 21
						'YaBrowser' => 'Yandex',
23 21
						'LinkedInApp' => 'LinkedIn',
24 21
						'[LinkedInApp]' => 'LinkedIn',
25 21
						'GoogleApp' => 'Google',
26 21
						'com.zhiliaoapp.musically' => 'TikTok',
27 21
						'com.google.android.apps.searchlite' => 'Google',
28 21
						'com.google.android.googlequicksearchbox' => 'Google',
29 21
						'com.google.photos' => 'Google Photos',
30 21
						'com.google.ios.youtube' => 'YouTube',
31 21
						'com.google.GoogleMobile' => 'Google',
32 21
						'AlohaBrowserApp' => 'Aloha',
33 21
						'OculusBrowser' => 'Oculus Browser',
34 21
						'AndroidDownloadManager' => 'Android Download Manager'
35 21
					];
36 21
					$app = $parts[0 + $offset];
37 21
					if (\mb_stripos($app, \rtrim($match, '/')) !== false) { // check the match is before the slash
38 21
						return [
39 21
							'type' => 'human',
40 21
							'app' => $map[$app] ?? $app,
41 21
							'appname' => \trim($app, '[]'),
42 21
							'appversion' => $parts[1 + $offset] ?? null
43 21
						];
44
					}
45
				}
46 38
				return [];
47 1
			},
48 1
			'langslash' => function (string $value) : array {
49 6
				$parts = \explode('-', \str_replace('_', '-', \explode('/', $value, 2)[1]), 4);
50 6
				$suffix = $parts[2] ?? $parts[1] ?? null;
51 6
				return [
52 6
					'language' => \strtolower($parts[0]).($suffix !== null ? '-'.\strtoupper($suffix) : '')
53 6
				];
54 1
			}
55 1
		];
56 3
		return [
57 3
			'com.google.GoogleMobile/' => new props('start', function (string $value, int $i, array $tokens, string $match) use ($fn) : array {
58
				return \array_merge($fn['appslash']($value, $i, $tokens, $match), [
59
					'category' => 'mobile'
60
				]);
61 3
			}),
62 3
			'com.google.' => new props('start', $fn['appslash']),
63 3
			'OcIdWebView' => new props('exact', function (string $value) : array {
64 1
				$data = [
65 1
					'app' => 'Google App Web View',
66 1
					'appname' => $value
67 1
				];
68 1
				return $data;
69 3
			}),
70 3
			'Instagram' => new props('any', function (string $value, int $i, array $tokens) : array {
71 8
				$parts = \explode(' ', $value, 4);
72 8
				$data = [
73 8
					'type' => 'human',
74 8
					'app' => 'Instagram',
75 8
					'appname' => 'Instagram',
76 8
					'appversion' => $parts[1] ?? null,
77 8
					'platform' => empty($parts[2]) ? null : platforms::getPlatform($parts[2])
78 8
				];
79 8
				foreach (\array_slice($tokens, $i + 1) AS $key => $item) {
80 8
					$ipad = null;
81 8
					if (($ipados = \str_starts_with($item, 'iPadOS')) || \str_starts_with($item, 'iOS ')) {
82 2
						$data['kernel'] = 'Linux';
83 2
						$data['platform'] = 'iOS';
84 2
						$data['platformversion'] = \str_replace('_', '.', \mb_substr($item, $ipados ? 7 : 4));
85 2
						$data['density'] = \floatval(\mb_substr($item, 6));
86 8
					} elseif (($iphone = \str_starts_with($item, 'iPhone')) || ($ipad = \str_starts_with($item, 'iPad')) || \str_starts_with($item, 'iPod')) {
87 2
						$data['vendor'] = 'Apple';
88 2
						$data['category'] = $ipad ? 'tablet' : 'mobile';
89 2
						$data['device'] = $iphone ? 'iPhone' : ($ipad ? 'iPad' : 'iPod');
90 2
						$data['model'] = \str_replace(',', '.', \mb_substr($item, $iphone ? 6 : 4));
91 2
						$data['architecture'] = 'arm';
92 2
						$data['bits'] = 64;
93 8
					} elseif (\str_starts_with($item, 'scale=')) {
94 2
						$data['density'] = \floatval(\mb_substr($item, 6));
95 8
					} elseif (\str_ends_with($item, 'dpi')) {
96 7
						$data['dpi'] = \intval(\mb_substr($item, 0, -3));
97 8
					} elseif (\str_contains($item, 'x') && \strspn($item, '0123456789x') === \strlen($item)) {
98 8
						list($data['width'], $data['height']) = \array_map('intval', \explode('x', $item, 2));
99
100
						// get device when the UA string starts with "Instagram"
101 8
						if ($i === 0 && !isset($data['vendor'])) {
102 1
							$device = [
103 1
								\trim(\mb_strstr($tokens[$key + 2] ?? '', '/') ?: $tokens[$key + 2] ?? '', '/ '), // sometimes the vendor name has a / with the correct name after
104 1
								$tokens[$key + 3] ?? null
105 1
							];
106
107
							// remove duplicated name
108 1
							if ($device[1] !== null && \mb_stripos($device[1], $device[0]) === 0) {
109 1
								unset($device[0]);
110
							}
111 1
							$data = \array_merge($data, devices::getDevice(\implode(' ', \array_filter($device))));
112 1
							break;
113
						}
114
					}
115
				}
116 8
				return $data;
117 3
			}),
118 3
			'imoAndroid/' => new props('start', fn (string $value, int $i, array $tokens) : array => \array_merge(
119 3
				isset($tokens[$i + 3]) ? devices::getDevice($tokens[$i + 3]) : [],
120 3
				[
121 3
					'app' => 'imo',
122 3
					'appname' => 'imoAndroid',
123 3
					'appversion' => ($ver = \mb_strstr($value, '/')) === false ? $value : \ltrim($ver, '/'),
124 3
					'platform' => 'Android',
125 3
					'platformversion' => $tokens[$i + 1] ?? null,
126 3
					'cpu' => $tokens[$i + 11] ?? null,
127 3
					'vendor' => isset($tokens[$i + 4]) ? devices::getVendor($tokens[$i + 4]) : null
128 3
				]
129 3
			)),
130 3
			'GSA/' => new props('any', fn (string $value) : array => [
131 3
				'app' => 'Google',
132 3
				'appname' => 'GSA',
133 3
				'appversion' => \mb_substr($value, 4)
134 3
			]),
135 3
			'DuckDuckGo/' => new props('start', $fn['appslash']),
136 3
			'FlipboardProxy/' => new props('start', $fn['appslash']),
137 3
			'Emacs/' => new props('start', $fn['appslash']),
138 3
			'AndroidDownloadManager/' => new props('start', $fn['appslash']),
139 3
			'Google-Read-Aloud' => new props('exact', [
140 3
				'type' => 'human',
141 3
				'app' => 'Google-Read-Aloud',
142 3
				'appname' => 'Google-Read-Aloud'
143 3
			]),
144 3
			'Zoom ' => new props('start', fn (string $value) : array => [
145 2
				'app' => 'Zoom',
146 2
				'appname' => 'Zoom',
147 2
				'appversion' => \mb_substr($value, 5)
148 2
			]),
149 3
			'OculusBrowser/' => new props('start', $fn['appslash']),
150 3
			'YaBrowser/' => new props('start', $fn['appslash']),
151 3
			'choqok/' => new props('start', $fn['appslash']),
152 3
			'PowerShell/' => new props('start', $fn['appslash']),
153 3
			'Quora ' => new props('start', fn (string $value) : array => [
154 1
				'type' => 'human',
155 1
				'app' => 'Quora',
156 1
				'appname' => 'Quora',
157 1
				'appversion' => \explode(' ', $value, 3)[1]
158 1
			]),
159 3
			'AmazonKidsBrowser/' => new props('start', $fn['appslash']),
160 3
			'Teams/' => new props('start', $fn['appslash']),
161 3
			'Viber/' => new props('start', $fn['appslash']),
162 3
			'AppleExchangeWebServices/' => new props('start', $fn['appslash']),
163 3
			'MicroMessenger/' => new props('start', fn (string $value) : array => [
164 3
				'app' => 'WeChat',
165 3
				'appname' => 'MicroMessenger',
166 3
				'appversion' => \mb_substr($value, 15)
167 3
			]),
168 3
			'weibo' => new props('any', function (string $value) : array {
169 1
				$data = [
170 1
					'app' => 'Weibo',
171 1
					'appname' => 'Weibo'
172 1
				];
173 1
				$parts = \explode('_', $value);
174 1
				foreach ($parts AS $i => $item) {
175 1
					if (\mb_stripos($item, 'Weibo') !== false) {
176 1
						$data['appname'] = $item;
177 1
						$data['appversion'] = $parts[$i + (\strspn($parts[$i + 1] ?? '', '0123456789', 0, 1) === 1 ? 1 : 2)] ?? null;
178 1
						break;
179
					}
180
				}
181 1
				return $data;
182 3
			}),
183
184
			// special parser for Facebook app because it is completely different to any other
185 3
			'FBAN/' => new props('any', function (string $value) : array {
186 4
				$map = [
187 4
					'FBAN/MessengerLiteForiOS' => [
188 4
						'type' => 'human',
189 4
						'app' => 'Facebook Messenger',
190 4
						'platform' => 'iOS'
191 4
					],
192 4
					'FBAN/FB4A' => [
193 4
						'type' => 'human',
194 4
						'app' => 'Facebook',
195 4
						'platform' => 'Android'
196 4
					],
197 4
					'FBAN/FBIOS' => [
198 4
						'type' => 'human',
199 4
						'app' => 'Facebook',
200 4
						'platform' => 'iOS'
201 4
					],
202 4
					'FBAN/FB4FireTV' => [
203 4
						'type' => 'human',
204 4
						'category' => 'tv',
205 4
						'app' => 'Facebook',
206 4
						'platform' => 'Android'
207 4
					],
208 4
					'FBAN/MessengerDesktop' => [
209 4
						'type' => 'human',
210 4
						'category' => 'desktop',
211 4
						'app' => 'Facebook Messenger'
212 4
					],
213 4
					'FacebookCanvasDesktop FBAN/GamesWindowsDesktopApp' => [
214 4
						'type' => 'human',
215 4
						'platform' => 'Windows',
216 4
						'category' => 'desktop',
217 4
						'app' => 'Facebook Gamesroom'
218 4
					]
219 4
				];
220 4
				return \array_merge([
221 4
					'type' => 'human',
222 4
					'app' => 'Facebook',
223 4
					'appname' => \explode('/', $value, 2)[1]
224 4
				], $map[$value] ?? []);
225 3
			}),
226 3
			'FB_IAB/' => new props('start', fn (string $value) : array => [
227 7
				'app' => 'Facebook',
228 7
				'appname' => \mb_substr($value, 7)
229 7
			]),
230 3
			'FBAV/' => new props('start', fn (string $value) : array => [
231 9
				'appversion' => \mb_substr($value, 5)
232 9
			]),
233 3
			'FBMF/' => new props('start', fn (string $value) : array => [
234 2
				'vendor' => devices::getVendor(\mb_substr($value, 5))
235 2
			]),
236 3
			'FBDV/' => new props('start', fn (string $value) : array => devices::getDevice(\mb_substr($value, 5))),
237 3
			'FBMD/' => new props('start', fn (string $value) : array => [
238 3
				'model' => \mb_substr($value, 5)
239 3
			]),
240 3
			'FBLC/' => new props('start', $fn['langslash']),
241 3
			'FBDM/' => new props('start', function (string $value) : array {
242 1
				$data = [];
243 1
				foreach (\explode(',', \trim(\mb_substr($value, 5), '{}')) AS $item) {
244 1
					$parts = \explode('=', $item);
245 1
					if (!empty($parts[1])) {
246 1
						if (\is_numeric($parts[1])) {
247 1
							$parts[1] = \str_contains($parts[1], '.') ? \floatval($parts[1]) : \intval($parts[1]);
248
						}
249 1
						$data[$parts[0]] = $parts[1];
250
					}
251
				}
252 1
				return $data;
253 3
			}),
254 3
			'width=' => new props('start', fn (string $value) : array => [
255 2
				'width' => \intval(\mb_substr($value, 6))
256 2
			]),
257 3
			'height=' => new props('start', fn (string $value) : array => [
258 2
				'height' => \intval(\mb_substr($value, 7))
259 2
			]),
260 3
			'dpi=' => new props('start', fn (string $value) : array => [
261 1
				'dpi' => \mb_substr($value, 4)
262 1
			]),
263 3
			'FBSN/' => new props('start', fn (string $value) : array => [
264 3
				'platform' => \mb_substr($value, 5)
265 3
			]),
266 3
			'FBSV' => new props('start', fn (string $value) : array => [
267 3
				'platformversion' => \mb_substr($value, 5)
268 3
			]),
269 3
			'isDarkMode/' => new props('start', function (string $value) : array {
270 1
				$mode = \mb_substr($value, 11);
271 1
				return [
272 1
					'darkmode' => \in_array($mode, ['0', '1'], true) ? \boolval($mode) : null
273 1
				];
274 3
			}),
275 3
			'AppTheme/' => new props('start', fn (string $value) : array => [
276 1
				'darkmode' => \mb_substr($value, 9) === 'dark'
277 1
			]),
278 3
			'ByteFullLocale/' => new props('start', $fn['langslash']),
279 3
			'NetType/' => new props('start', fn (string $value) : array => [
280 5
				'nettype' => \mb_convert_case(\mb_substr($value, 8), MB_CASE_UPPER)
281 5
			]),
282 3
			'Microsoft Office' => new props('start', function (string $value, int $i, array $tokens) : array {
283 1
				$data = [
284 1
					'type' => 'human'
285 1
				];
286 1
				if (\str_contains($value, '/')) {
287 1
					foreach (\array_slice($tokens, $i + 1) AS $item) {
288 1
						if (\str_starts_with($item, 'Microsoft ')) {
289 1
							$parts = \explode(' ', $item);
290 1
							$data['app'] = $parts[0].' '.$parts[1];
291 1
							$data['appname'] = $parts[0].' '.$parts[1];
292 1
							if (isset($parts[2])) {
293 1
								$data['appversion'] = $parts[2];
294
							}
295 1
							break;
296
						}
297
					}
298 1
					if (!isset($data['app'])) {
299 1
						$parts = \explode('/', $value, 2);
300 1
						$data['app'] = $parts[0];
301 1
						$data['appname'] = $parts[0];
302 1
						if (!isset($data['appversion'])) {
303 1
							$data['appversion'] = $parts[1];
304
						}
305
					}
306
				} else {
307 1
					$parts = \explode(' ', $value);
308 1
					$data['app'] = \rtrim($parts[0].' '.($parts[1] ?? '').' '.($parts[2] ?? ''));
309 1
					$data['appname'] = $value;
310 1
					$data['appversion'] = $parts[3] ?? null;
311
				}
312 1
				return $data;
313 3
			}),
314
315
			// TikTok
316 3
			'AppName/' => new props('start', fn(string $value) : array => [
317 3
				'app' => $value === 'AppName/musical_ly' ? 'TikTok' : \mb_substr($value, 8),
318 3
				'appname' => \mb_substr($value, 8)
319 3
			]),
320 3
			'app_version/' => new props('start', fn(string $value) : array => [
321 3
				'appversion' => \mb_substr($value, 12)
322 3
			]),
323 3
			'AppVersion/' => new props('any', fn(string $value) : array => [
324 2
				'appversion' => \explode('/', $value, 2)[1]
325 2
			]),
326 3
			'musical_ly' => new props('start', fn(string $value) : array => [
327 2
				'app' => 'TikTok',
328 2
				'appname' => 'musical_ly',
329 2
				'appversion' => \str_replace('_', '.', \mb_substr(\explode(' ', $value, 2)[0], 11))
330 2
			]),
331
				
332
			// generic
333 3
			'App' => new props('any', $fn['appslash'])
334 3
		];
335
	}
336
}