Passed
Push — main ( 7300c8...1902e7 )
by Will
13:33
created

agentzero::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 44
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 27
c 0
b 0
f 0
dl 0
loc 44
ccs 28
cts 28
cp 1
rs 9.488
cc 1
nc 1
nop 1
crap 1
1
<?php
2
declare(strict_types = 1);
3
namespace hexydec\agentzero;
4
5
/**
6
 * @phpstan-import-type MatchValue from config
7
 */
8
class agentzero {
9
10
	// categories
11
	public readonly ?string $type;
12
	public readonly ?string $category;
13
14
	// device
15
	public readonly ?string $vendor;
16
	public readonly ?string $device;
17
	public readonly ?string $model;
18
	public readonly ?string $build;
19
20
	// architecture
21
	public readonly ?string $processor;
22
	public readonly ?string $architecture;
23
	public readonly ?int $bits;
24
25
	// platform
26
	public readonly ?string $kernel;
27
	public readonly ?string $platform;
28
	public readonly ?string $platformversion;
29
30
	// browser
31
	public readonly ?string $engine;
32
	public readonly ?string $engineversion;
33
	public readonly ?string $browser;
34
	public readonly ?string $browserversion;
35
	public readonly ?string $language;
36
37
	// app
38
	public readonly ?string $app;
39
	public readonly ?string $appversion;
40
	public readonly ?string $url;
41
42
	// network
43
	public readonly ?string $nettype;
44
	public readonly ?string $proxy;
45
46
	// screen
47
	public readonly ?int $width;
48
	public readonly ?int $height;
49
	public readonly ?int $dpi;
50
	public readonly ?float $density;
51
	public readonly ?bool $darkmode;
52
53
	/**
54
	 * Constructs a new AgentZero object, private because it can only be created internally
55
	 * 
56
	 * @param \stdClass $data A stdClass object containing the UA details
57
	 */
58 84
	private function __construct(\stdClass $data) {
59
60
		// categories
61 84
		$this->type = $data->type ?? null;
0 ignored issues
show
Bug introduced by
The property type is declared read-only in hexydec\agentzero\agentzero.
Loading history...
62 84
		$this->category = $data->category ?? null;
0 ignored issues
show
Bug introduced by
The property category is declared read-only in hexydec\agentzero\agentzero.
Loading history...
63
64
		// device
65 84
		$this->vendor = $data->vendor ?? null;
0 ignored issues
show
Bug introduced by
The property vendor is declared read-only in hexydec\agentzero\agentzero.
Loading history...
66 84
		$this->device = $data->device ?? null;
0 ignored issues
show
Bug introduced by
The property device is declared read-only in hexydec\agentzero\agentzero.
Loading history...
67 84
		$this->model = $data->model ?? null;
0 ignored issues
show
Bug introduced by
The property model is declared read-only in hexydec\agentzero\agentzero.
Loading history...
68 84
		$this->build = $data->build ?? null;
0 ignored issues
show
Bug introduced by
The property build is declared read-only in hexydec\agentzero\agentzero.
Loading history...
69
70
		// architecture
71 84
		$this->processor = $data->processor ?? null;
0 ignored issues
show
Bug introduced by
The property processor is declared read-only in hexydec\agentzero\agentzero.
Loading history...
72 84
		$this->architecture = $data->architecture ?? null;
0 ignored issues
show
Bug introduced by
The property architecture is declared read-only in hexydec\agentzero\agentzero.
Loading history...
73 84
		$this->bits = $data->bits ?? null;
0 ignored issues
show
Bug introduced by
The property bits is declared read-only in hexydec\agentzero\agentzero.
Loading history...
74
75
		// platform
76 84
		$this->kernel = $data->kernel ?? null;
0 ignored issues
show
Bug introduced by
The property kernel is declared read-only in hexydec\agentzero\agentzero.
Loading history...
77 84
		$this->platform = $data->platform ?? null;
0 ignored issues
show
Bug introduced by
The property platform is declared read-only in hexydec\agentzero\agentzero.
Loading history...
78 84
		$this->platformversion = $data->platformversion ?? null;
0 ignored issues
show
Bug introduced by
The property platformversion is declared read-only in hexydec\agentzero\agentzero.
Loading history...
79
80
		// browser
81 84
		$this->engine = $data->engine ?? null;
0 ignored issues
show
Bug introduced by
The property engine is declared read-only in hexydec\agentzero\agentzero.
Loading history...
82 84
		$this->engineversion = $data->engineversion ?? null;
0 ignored issues
show
Bug introduced by
The property engineversion is declared read-only in hexydec\agentzero\agentzero.
Loading history...
83 84
		$this->browser = $data->browser ?? null;
0 ignored issues
show
Bug introduced by
The property browser is declared read-only in hexydec\agentzero\agentzero.
Loading history...
84 84
		$this->browserversion = $data->browserversion ?? null;
0 ignored issues
show
Bug introduced by
The property browserversion is declared read-only in hexydec\agentzero\agentzero.
Loading history...
85 84
		$this->language = $data->language ?? null;
0 ignored issues
show
Bug introduced by
The property language is declared read-only in hexydec\agentzero\agentzero.
Loading history...
86
87
		// app
88 84
		$this->app = $data->app ?? null;
0 ignored issues
show
Bug introduced by
The property app is declared read-only in hexydec\agentzero\agentzero.
Loading history...
89 84
		$this->appversion = $data->appversion ?? null;
0 ignored issues
show
Bug introduced by
The property appversion is declared read-only in hexydec\agentzero\agentzero.
Loading history...
90 84
		$this->url = $data->url ?? null;
0 ignored issues
show
Bug introduced by
The property url is declared read-only in hexydec\agentzero\agentzero.
Loading history...
91
92
		// network
93 84
		$this->nettype = $data->nettype ?? null;
0 ignored issues
show
Bug introduced by
The property nettype is declared read-only in hexydec\agentzero\agentzero.
Loading history...
94 84
		$this->proxy = $data->proxy ?? null;
0 ignored issues
show
Bug introduced by
The property proxy is declared read-only in hexydec\agentzero\agentzero.
Loading history...
95
96
		// screen
97 84
		$this->width = $data->width ?? null;
0 ignored issues
show
Bug introduced by
The property width is declared read-only in hexydec\agentzero\agentzero.
Loading history...
98 84
		$this->height = $data->height ?? null;
0 ignored issues
show
Bug introduced by
The property height is declared read-only in hexydec\agentzero\agentzero.
Loading history...
99 84
		$this->dpi = $data->dpi ?? null;
0 ignored issues
show
Bug introduced by
The property dpi is declared read-only in hexydec\agentzero\agentzero.
Loading history...
100 84
		$this->density = $data->density ?? null;
0 ignored issues
show
Bug introduced by
The property density is declared read-only in hexydec\agentzero\agentzero.
Loading history...
101 84
		$this->darkmode = $data->darkmode ?? null;
0 ignored issues
show
Bug introduced by
The property darkmode is declared read-only in hexydec\agentzero\agentzero.
Loading history...
102
	}
103
104
	/**
105
	 * Retrieves calculated properties
106
	 * 
107
	 * @param string $key The name of the property to retrieve
108
	 * @return string|int|null The requested property or null if it doesn't exist
109
	 */
110
	public function __get(string $key) : string|int|null {
111
		switch ($key) {
112
			case 'host':
113
				if ($this->url !== null && ($host = \parse_url($this->url, PHP_URL_HOST)) !== false && $host !== null) {
114
					return \str_starts_with($host, 'www.') ? \substr($host, 4) : $host;
115
				}
116
				return null;
117
			case 'browsermajorversion':
118
			case 'enginemajorversion':
119
			case 'platformmajorversion':
120
			case 'appmajorversion':
121
				$item = \str_replace('major', '', $key);
122
				$value = $this->{$item} ?? null;
123
				return $value === null ? null : \intval(\substr($value, 0, \strspn($value, '0123456789')));
124
		}
125
		return $this->{$key} ?? null;
126
	}
127
128
	/**
129
	 * Extracts tokens from a UA string
130
	 * 
131
	 * @param string $ua The User Agent string to be tokenised
132
	 * @param array<string> $single An array of strings that can appear on their own, enables the tokens to be split correctly
133
	 * @param array<string> $ignore An array of tokens that can be ignored in the UA string
134
	 * @return false|array<int,string> An array of tokens, or false if no tokens could be extracted
135
	 */
136 84
	protected static function getTokens(string $ua, array $single, array $ignore) : array|false {
137
138
		// prepare regexp
139 84
		$single = \implode('|', \array_map('preg_quote', $single));
140 84
		$pattern = '/\{[^}]++\}|[^()\[\];,\/  _-](?:(?<!'.$single.') (?!https?:\/\/)|[^()\[\];,\/ ]*)*[^()\[\];,\/  _-](?:\/[^;,()\[\]  ]++)?/i';
141
142
		// split up ua string
143 84
		if (\preg_match_all($pattern, $ua, $match)) {
144
145
			// remove ignore values
146 84
			$tokens = \array_diff($match[0], $ignore);
147
148
			// special case for handling like
149 84
			foreach ($tokens AS $key => $item) {
150 84
				if (\str_starts_with($item, 'like ')) {
151
152
					// chop off words up to a useful token e.g. Platform/Version
153 7
					if (\str_contains($item, '/') && ($pos = \mb_strrpos($item, ' ')) !== false) {
154 5
						$tokens[$key] = \mb_substr($item, $pos + 1);
155
156
					// just remove the token
157
					} else {
158 2
						unset($tokens[$key]);
159
					}
160
				}
161
			}
162
163
			// rekey and return
164 84
			return \array_values($tokens);
165
		}
166
		return false;
167
	}
168
169
	/**
170
	 * Parses a User Agent string
171
	 * 
172
	 * @param string $ua The User Agent string to be parsed
173
	 * @return agentzero|false An agentzero object containing the parsed values of the input UA, or false if it could not be parsed
174
	 */
175 84
	public static function parse(string $ua) : agentzero|false {
176 84
		if (($config = config::get()) === null) {
177
178 84
		} elseif (($tokens = self::getTokens($ua, $config['single'], $config['ignore'])) !== false) {
179
180
			// extract UA info
181 84
			$browser = new \stdClass();
182 84
			foreach ($config['match'] AS $key => $item) {
183 84
				$keylower = \mb_strtolower($key);
184 84
				foreach ($tokens AS $i => $token) {
185 84
					$tokenlower = \mb_strtolower($token);
186 84
					switch ($item['match']) {
187
188
						// match from start of string
189 84
						case 'start':
190 84
							if (\str_starts_with($tokenlower, $keylower)) {
191 84
								self::setProps($browser, $item['categories'], $token, $i, $tokens, $key);
192
							}
193 84
							break;
194
195
						// match anywhere in the string
196 84
						case 'any':
197 84
							if (\str_contains($tokenlower, $keylower)) {
198 82
								self::setProps($browser, $item['categories'], $token, $i, $tokens, $key);
199
							}
200 84
							break;
201
202
						// match anywhere in the string
203 84
						case 'exact':
204 84
							if ($tokenlower === $keylower) {
205 73
								self::setProps($browser, $item['categories'], $token, $i, $tokens, $key);
206 73
								break 2;
207
							} else {
208 84
								break;
209
							}
210
					}
211
				}
212
			}
213 84
			return new agentzero($browser);
214
		}
215
		return false;
216
	}
217
218
	/**
219
	 * Sets parsed UA properties, and calls callbacks to generate properties and sets them to the output object
220
	 * 
221
	 * @param \stdClass $browser A stdClass object to which the properties will be set
222
	 * @param MatchValue $props An array of properties or a Closure to generate properties
0 ignored issues
show
Bug introduced by
The type hexydec\agentzero\MatchValue was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
223
	 * @param string $value The current token value
224
	 * @param int $i The ID of the current token
225
	 * @param array<string> &$tokens The tokens array
226
	 * @return void
227
	 */
228 84
	protected static function setProps(\stdClass $browser, array|\Closure $props, string $value, int $i, array $tokens, string $key) : void {
229 84
		if ($props instanceof \Closure) {
0 ignored issues
show
introduced by
$props is never a sub-type of Closure.
Loading history...
230 84
			$props = $props($value, $i, $tokens, $key);
231
		}
232 84
		if (\is_array($props)) {
0 ignored issues
show
introduced by
The condition is_array($props) is always true.
Loading history...
233 84
			foreach ($props AS $key => $item) {
234 84
				if ($item !== null && !isset($browser->{$key})) {
235 84
					$browser->{$key} = $item;
236
				}
237
			}
238
		}
239
	}
240
}