Passed
Push — main ( 0b3ef0...fa30e1 )
by Will
02:47
created

apps   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 311
Duplicated Lines 0 %

Test Coverage

Coverage 98.91%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 42
eloc 229
c 1
b 0
f 0
dl 0
loc 311
ccs 273
cts 276
cp 0.9891
rs 9.0399

1 Method

Rating   Name   Duplication   Size   Complexity  
F get() 0 304 42

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