PluginProbe ʕ •ᴥ•ʔ
Advanced Access Manager – Access Governance for WordPress / trunk
Advanced Access Manager – Access Governance for WordPress vtrunk
6.8.4 6.8.5 6.9.0 6.9.1 6.9.10 6.9.11 6.9.12 6.9.13 6.9.14 6.9.15 6.9.16 6.9.17 6.9.18 6.9.19 6.9.2 6.9.20 6.9.21 6.9.22 6.9.23 6.9.24 6.9.25 6.9.26 6.9.27 6.9.28 6.9.29 6.9.3 6.9.30 6.9.31 6.9.32 6.9.33 6.9.34 6.9.35 6.9.36 6.9.37 6.9.38 6.9.39 6.9.4 6.9.41 6.9.42 6.9.43 6.9.44 6.9.45 6.9.46 6.9.47 6.9.48 6.9.49 6.9.5 6.9.51 6.9.6 6.9.7 6.9.8 6.9.9 7.0.0 7.0.0-alpha.6 7.0.0-alpha.7 7.0.0-beta.1 7.0.0-rc1 7.0.0-rc2 7.0.0-rc3 7.0.1 7.0.10 7.0.11 7.0.2 7.0.3 7.0.4 7.0.5 7.0.6 7.0.7 7.0.8 7.0.9 7.1.0 7.1.1 trunk 3.0 4.0 4.0.1 4.1 4.2 4.3 4.4 4.4.1 4.5 4.6 4.6.1 4.6.2 4.7 4.7.1 4.7.2 4.7.5 4.7.6 4.8 4.8.1 4.9 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 4.9.5.1 4.9.5.2 5.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1 5.1.1 5.10 5.11 5.2 5.2.1 5.2.5 5.2.6 5.2.7 5.3 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.4 5.4.1 5.4.2 5.4.3 5.4.3.1 5.4.3.2 5.5 5.5.1 5.5.2 5.6 5.6.1 5.6.1.1 5.7 5.7.1 5.7.2 5.7.3 5.8 5.8.1 5.8.2 5.8.3 5.9 5.9.1 5.9.1.1 5.9.2 5.9.2.1 5.9.3 5.9.4 5.9.5 5.9.6 5.9.6.1 5.9.6.2 5.9.6.3 5.9.7 5.9.7.1 5.9.7.2 5.9.7.3 5.9.8 5.9.8.1 5.9.9 5.9.9.1 6.0.0 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.1.0 6.1.1 6.2.0 6.2.1 6.2.2 6.3.0 6.3.1 6.3.2 6.3.3 6.4.0 6.4.1 6.4.2 6.4.3 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.6.0 6.6.1 6.6.2 6.6.3 6.6.4 6.7.0 6.7.1 6.7.2 6.7.3 6.7.4 6.7.5 6.7.6 6.7.7 6.7.8 6.7.9 6.8.0 6.8.1 6.8.2 6.8.3
advanced-access-manager / vendor / matomo / device-detector / Parser / OperatingSystem.php
advanced-access-manager / vendor / matomo / device-detector / Parser Last commit date
Client 1 year ago Device 1 year ago AbstractBotParser.php 1 year ago AbstractParser.php 1 year ago Bot.php 1 year ago OperatingSystem.php 1 year ago VendorFragment.php 1 year ago
OperatingSystem.php
755 lines
1 <?php
2
3 /**
4 * Device Detector - The Universal Device Detection library for parsing User Agents
5 *
6 * @link https://matomo.org
7 *
8 * @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
9 */
10
11 declare(strict_types=1);
12
13 namespace DeviceDetector\Parser;
14
15 use DeviceDetector\ClientHints;
16
17 /**
18 * Class OperatingSystem
19 *
20 * Parses the useragent for operating system information
21 *
22 * Detected operating systems can be found in self::$operatingSystems and /regexes/oss.yml
23 * This class also defined some operating system families and methods to get the family for a specific os
24 */
25 class OperatingSystem extends AbstractParser
26 {
27 /**
28 * @var string
29 */
30 protected $fixtureFile = 'regexes/oss.yml';
31
32 /**
33 * @var string
34 */
35 protected $parserName = 'os';
36
37 /**
38 * Known operating systems mapped to their internal short codes
39 *
40 * @var array
41 */
42 protected static $operatingSystems = [
43 'AIX' => 'AIX',
44 'AND' => 'Android',
45 'ADR' => 'Android TV',
46 'ALP' => 'Alpine Linux',
47 'AMZ' => 'Amazon Linux',
48 'AMG' => 'AmigaOS',
49 'ARM' => 'Armadillo OS',
50 'ARO' => 'AROS',
51 'ATV' => 'tvOS',
52 'ARL' => 'Arch Linux',
53 'AOS' => 'AOSC OS',
54 'ASP' => 'ASPLinux',
55 'AZU' => 'Azure Linux',
56 'BTR' => 'BackTrack',
57 'SBA' => 'Bada',
58 'BYI' => 'Baidu Yi',
59 'BEO' => 'BeOS',
60 'BLB' => 'BlackBerry OS',
61 'QNX' => 'BlackBerry Tablet OS',
62 'PAN' => 'blackPanther OS',
63 'BOS' => 'Bliss OS',
64 'BMP' => 'Brew',
65 'BSN' => 'BrightSignOS',
66 'CAI' => 'Caixa Mágica',
67 'CES' => 'CentOS',
68 'CST' => 'CentOS Stream',
69 'CLO' => 'Clear Linux OS',
70 'CLR' => 'ClearOS Mobile',
71 'COS' => 'Chrome OS',
72 'CRS' => 'Chromium OS',
73 'CHN' => 'China OS',
74 'COL' => 'Coolita OS',
75 'CYN' => 'CyanogenMod',
76 'DEB' => 'Debian',
77 'DEE' => 'Deepin',
78 'DFB' => 'DragonFly',
79 'DVK' => 'DVKBuntu',
80 'ELE' => 'ElectroBSD',
81 'EUL' => 'EulerOS',
82 'FED' => 'Fedora',
83 'FEN' => 'Fenix',
84 'FOS' => 'Firefox OS',
85 'FIR' => 'Fire OS',
86 'FOR' => 'Foresight Linux',
87 'FRE' => 'Freebox',
88 'BSD' => 'FreeBSD',
89 'FRI' => 'FRITZ!OS',
90 'FYD' => 'FydeOS',
91 'FUC' => 'Fuchsia',
92 'GNT' => 'Gentoo',
93 'GNX' => 'GENIX',
94 'GEO' => 'GEOS',
95 'GNS' => 'gNewSense',
96 'GRI' => 'GridOS',
97 'GTV' => 'Google TV',
98 'HPX' => 'HP-UX',
99 'HAI' => 'Haiku OS',
100 'IPA' => 'iPadOS',
101 'HAR' => 'HarmonyOS',
102 'HAS' => 'HasCodingOS',
103 'HEL' => 'HELIX OS',
104 'IRI' => 'IRIX',
105 'INF' => 'Inferno',
106 'JME' => 'Java ME',
107 'JOL' => 'Joli OS',
108 'KOS' => 'KaiOS',
109 'KAL' => 'Kali',
110 'KAN' => 'Kanotix',
111 'KIN' => 'KIN OS',
112 'KNO' => 'Knoppix',
113 'KTV' => 'KreaTV',
114 'KBT' => 'Kubuntu',
115 'LIN' => 'GNU/Linux',
116 'LEA' => 'LeafOS',
117 'LND' => 'LindowsOS',
118 'LNS' => 'Linspire',
119 'LEN' => 'Lineage OS',
120 'LIR' => 'Liri OS',
121 'LOO' => 'Loongnix',
122 'LBT' => 'Lubuntu',
123 'LOS' => 'Lumin OS',
124 'LUN' => 'LuneOS',
125 'VLN' => 'VectorLinux',
126 'MAC' => 'Mac',
127 'MAE' => 'Maemo',
128 'MAG' => 'Mageia',
129 'MDR' => 'Mandriva',
130 'SMG' => 'MeeGo',
131 'MET' => 'Meta Horizon',
132 'MCD' => 'MocorDroid',
133 'MON' => 'moonOS',
134 'EZX' => 'Motorola EZX',
135 'MIN' => 'Mint',
136 'MLD' => 'MildWild',
137 'MOR' => 'MorphOS',
138 'NBS' => 'NetBSD',
139 'MTK' => 'MTK / Nucleus',
140 'MRE' => 'MRE',
141 'NXT' => 'NeXTSTEP',
142 'NWS' => 'NEWS-OS',
143 'WII' => 'Nintendo',
144 'NDS' => 'Nintendo Mobile',
145 'NOV' => 'Nova',
146 'OS2' => 'OS/2',
147 'T64' => 'OSF1',
148 'OBS' => 'OpenBSD',
149 'OVS' => 'OpenVMS',
150 'OVZ' => 'OpenVZ',
151 'OWR' => 'OpenWrt',
152 'OTV' => 'Opera TV',
153 'ORA' => 'Oracle Linux',
154 'ORD' => 'Ordissimo',
155 'PAR' => 'Pardus',
156 'PCL' => 'PCLinuxOS',
157 'PIC' => 'PICO OS',
158 'PLA' => 'Plasma Mobile',
159 'PSP' => 'PlayStation Portable',
160 'PS3' => 'PlayStation',
161 'PVE' => 'Proxmox VE',
162 'PUF' => 'Puffin OS',
163 'PUR' => 'PureOS',
164 'QTP' => 'Qtopia',
165 'PIO' => 'Raspberry Pi OS',
166 'RAS' => 'Raspbian',
167 'RHT' => 'Red Hat',
168 'RST' => 'Red Star',
169 'RED' => 'RedOS',
170 'REV' => 'Revenge OS',
171 'RIS' => 'risingOS',
172 'ROS' => 'RISC OS',
173 'ROC' => 'Rocky Linux',
174 'ROK' => 'Roku OS',
175 'RSO' => 'Rosa',
176 'ROU' => 'RouterOS',
177 'REM' => 'Remix OS',
178 'RRS' => 'Resurrection Remix OS',
179 'REX' => 'REX',
180 'RZD' => 'RazoDroiD',
181 'RXT' => 'RTOS & Next',
182 'SAB' => 'Sabayon',
183 'SSE' => 'SUSE',
184 'SAF' => 'Sailfish OS',
185 'SCI' => 'Scientific Linux',
186 'SEE' => 'SeewoOS',
187 'SER' => 'SerenityOS',
188 'SIR' => 'Sirin OS',
189 'SLW' => 'Slackware',
190 'SOS' => 'Solaris',
191 'SBL' => 'Star-Blade OS',
192 'SYL' => 'Syllable',
193 'SYM' => 'Symbian',
194 'SYS' => 'Symbian OS',
195 'S40' => 'Symbian OS Series 40',
196 'S60' => 'Symbian OS Series 60',
197 'SY3' => 'Symbian^3',
198 'TEN' => 'TencentOS',
199 'TDX' => 'ThreadX',
200 'TIZ' => 'Tizen',
201 'TIV' => 'TiVo OS',
202 'TOS' => 'TmaxOS',
203 'TUR' => 'Turbolinux',
204 'UBT' => 'Ubuntu',
205 'ULT' => 'ULTRIX',
206 'UOS' => 'UOS',
207 'VID' => 'VIDAA',
208 'VIZ' => 'ViziOS',
209 'WAS' => 'watchOS',
210 'WER' => 'Wear OS',
211 'WTV' => 'WebTV',
212 'WHS' => 'Whale OS',
213 'WIN' => 'Windows',
214 'WCE' => 'Windows CE',
215 'WIO' => 'Windows IoT',
216 'WMO' => 'Windows Mobile',
217 'WPH' => 'Windows Phone',
218 'WRT' => 'Windows RT',
219 'WPO' => 'WoPhone',
220 'XBX' => 'Xbox',
221 'XBT' => 'Xubuntu',
222 'YNS' => 'YunOS',
223 'ZEN' => 'Zenwalk',
224 'ZOR' => 'ZorinOS',
225 'IOS' => 'iOS',
226 'POS' => 'palmOS',
227 'WEB' => 'Webian',
228 'WOS' => 'webOS',
229 ];
230
231 /**
232 * Operating system families mapped to the short codes of the associated operating systems
233 *
234 * @var array
235 */
236 protected static $osFamilies = [
237 'Android' => [
238 'AND', 'CYN', 'FIR', 'REM', 'RZD', 'MLD', 'MCD', 'YNS', 'GRI', 'HAR',
239 'ADR', 'CLR', 'BOS', 'REV', 'LEN', 'SIR', 'RRS', 'WER', 'PIC', 'ARM',
240 'HEL', 'BYI', 'RIS', 'PUF', 'LEA', 'MET',
241 ],
242 'AmigaOS' => ['AMG', 'MOR', 'ARO'],
243 'BlackBerry' => ['BLB', 'QNX'],
244 'Brew' => ['BMP'],
245 'BeOS' => ['BEO', 'HAI'],
246 'Chrome OS' => ['COS', 'CRS', 'FYD', 'SEE'],
247 'Firefox OS' => ['FOS', 'KOS'],
248 'Gaming Console' => ['WII', 'PS3'],
249 'Google TV' => ['GTV'],
250 'IBM' => ['OS2'],
251 'iOS' => ['IOS', 'ATV', 'WAS', 'IPA'],
252 'RISC OS' => ['ROS'],
253 'GNU/Linux' => [
254 'LIN', 'ARL', 'DEB', 'KNO', 'MIN', 'UBT', 'KBT', 'XBT', 'LBT', 'FED',
255 'RHT', 'VLN', 'MDR', 'GNT', 'SAB', 'SLW', 'SSE', 'CES', 'BTR', 'SAF',
256 'ORD', 'TOS', 'RSO', 'DEE', 'FRE', 'MAG', 'FEN', 'CAI', 'PCL', 'HAS',
257 'LOS', 'DVK', 'ROK', 'OWR', 'OTV', 'KTV', 'PUR', 'PLA', 'FUC', 'PAR',
258 'FOR', 'MON', 'KAN', 'ZEN', 'LND', 'LNS', 'CHN', 'AMZ', 'TEN', 'CST',
259 'NOV', 'ROU', 'ZOR', 'RED', 'KAL', 'ORA', 'VID', 'TIV', 'BSN', 'RAS',
260 'UOS', 'PIO', 'FRI', 'LIR', 'WEB', 'SER', 'ASP', 'AOS', 'LOO', 'EUL',
261 'SCI', 'ALP', 'CLO', 'ROC', 'OVZ', 'PVE', 'RST', 'EZX', 'GNS', 'JOL',
262 'TUR', 'QTP', 'WPO', 'PAN', 'VIZ', 'AZU', 'COL',
263 ],
264 'Mac' => ['MAC'],
265 'Mobile Gaming Console' => ['PSP', 'NDS', 'XBX'],
266 'OpenVMS' => ['OVS'],
267 'Real-time OS' => ['MTK', 'TDX', 'MRE', 'JME', 'REX', 'RXT'],
268 'Other Mobile' => ['WOS', 'POS', 'SBA', 'TIZ', 'SMG', 'MAE', 'LUN', 'GEO'],
269 'Symbian' => ['SYM', 'SYS', 'SY3', 'S60', 'S40'],
270 'Unix' => [
271 'SOS', 'AIX', 'HPX', 'BSD', 'NBS', 'OBS', 'DFB', 'SYL', 'IRI', 'T64',
272 'INF', 'ELE', 'GNX', 'ULT', 'NWS', 'NXT', 'SBL',
273 ],
274 'WebTV' => ['WTV'],
275 'Windows' => ['WIN'],
276 'Windows Mobile' => ['WPH', 'WMO', 'WCE', 'WRT', 'WIO', 'KIN'],
277 'Other Smart TV' => ['WHS'],
278 ];
279
280 /**
281 * Contains a list of mappings from OS names we use to known client hint values
282 *
283 * @var array<string, array<string>>
284 */
285 protected static $clientHintMapping = [
286 'GNU/Linux' => ['Linux'],
287 'Mac' => ['MacOS'],
288 ];
289
290 /**
291 * Operating system families that are known as desktop only
292 *
293 * @var array
294 */
295 protected static $desktopOsArray = [
296 'AmigaOS', 'IBM', 'GNU/Linux', 'Mac', 'Unix', 'Windows', 'BeOS', 'Chrome OS',
297 ];
298
299 /**
300 * Fire OS version mapping
301 *
302 * @var array
303 */
304 private $fireOsVersionMapping = [
305 '11' => '8',
306 '10' => '8',
307 '9' => '7',
308 '7' => '6',
309 '5' => '5',
310 '4.4.3' => '4.5.1',
311 '4.4.2' => '4',
312 '4.2.2' => '3',
313 '4.0.3' => '3',
314 '4.0.2' => '3',
315 '4' => '2',
316 '2' => '1',
317 ];
318
319 /**
320 * Lineage OS version mapping
321 *
322 * @var array
323 */
324 private $lineageOsVersionMapping = [
325 '15' => '22',
326 '14' => '21',
327 '13' => '20.0',
328 '12.1' => '19.1',
329 '12' => '19.0',
330 '11' => '18.0',
331 '10' => '17.0',
332 '9' => '16.0',
333 '8.1.0' => '15.1',
334 '8.0.0' => '15.0',
335 '7.1.2' => '14.1',
336 '7.1.1' => '14.1',
337 '7.0' => '14.0',
338 '6.0.1' => '13.0',
339 '6.0' => '13.0',
340 '5.1.1' => '12.1',
341 '5.0.2' => '12.0',
342 '5.0' => '12.0',
343 '4.4.4' => '11.0',
344 '4.3' => '10.2',
345 '4.2.2' => '10.1',
346 '4.0.4' => '9.1.0',
347 ];
348
349 /**
350 * Returns all available operating systems
351 *
352 * @return array
353 */
354 public static function getAvailableOperatingSystems(): array
355 {
356 return self::$operatingSystems;
357 }
358
359 /**
360 * Returns all available operating system families
361 *
362 * @return array
363 */
364 public static function getAvailableOperatingSystemFamilies(): array
365 {
366 return self::$osFamilies;
367 }
368
369 /**
370 * Returns the os name and shot name
371 *
372 * @param string $name
373 *
374 * @return array
375 */
376 public static function getShortOsData(string $name): array
377 {
378 $short = 'UNK';
379
380 foreach (self::$operatingSystems as $osShort => $osName) {
381 if (\strtolower($name) !== \strtolower($osName)) {
382 continue;
383 }
384
385 $name = $osName;
386 $short = $osShort;
387
388 break;
389 }
390
391 return \compact('short', 'name');
392 }
393
394 /**
395 * @inheritdoc
396 */
397 public function parse(): ?array
398 {
399 $this->restoreUserAgentFromClientHints();
400
401 $osFromClientHints = $this->parseOsFromClientHints();
402 $osFromUserAgent = $this->parseOsFromUserAgent();
403
404 if (!empty($osFromClientHints['name'])) {
405 $name = $osFromClientHints['name'];
406 $version = $osFromClientHints['version'];
407
408 // use version from user agent if non was provided in client hints, but os family from useragent matches
409 if (empty($version)
410 && self::getOsFamily($name) === self::getOsFamily($osFromUserAgent['name'])
411 ) {
412 $version = $osFromUserAgent['version'];
413 }
414
415 // On Windows, version 0.0.0 can be either 7, 8 or 8.1
416 if ('Windows' === $name && '0.0.0' === $version) {
417 $version = ('10' === $osFromUserAgent['version']) ? '' : $osFromUserAgent['version'];
418 }
419
420 // If the OS name detected from client hints matches the OS family from user agent
421 // but the os name is another, we use the one from user agent, as it might be more detailed
422 if (self::getOsFamily($osFromUserAgent['name']) === $name && $osFromUserAgent['name'] !== $name) {
423 $name = $osFromUserAgent['name'];
424
425 if ('LeafOS' === $name || 'HarmonyOS' === $name) {
426 $version = '';
427 }
428
429 if ('PICO OS' === $name) {
430 $version = $osFromUserAgent['version'];
431 }
432
433 if ('Fire OS' === $name && !empty($osFromClientHints['version'])) {
434 $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0');
435
436 $version = $this->fireOsVersionMapping[$version]
437 ?? $this->fireOsVersionMapping[$majorVersion] ?? '';
438 }
439 }
440
441 $short = $osFromClientHints['short_name'];
442
443 // Chrome OS is in some cases reported as Linux in client hints, we fix this only if the version matches
444 if ('GNU/Linux' === $name
445 && 'Chrome OS' === $osFromUserAgent['name']
446 && $osFromClientHints['version'] === $osFromUserAgent['version']
447 ) {
448 $name = $osFromUserAgent['name'];
449 $short = $osFromUserAgent['short_name'];
450 }
451
452 // Chrome OS is in some cases reported as Android in client hints
453 if ('Android' === $name && 'Chrome OS' === $osFromUserAgent['name']) {
454 $name = $osFromUserAgent['name'];
455 $version = '';
456 $short = $osFromUserAgent['short_name'];
457 }
458
459 // Meta Horizon is reported as Linux in client hints
460 if ('GNU/Linux' === $name && 'Meta Horizon' === $osFromUserAgent['name']) {
461 $name = $osFromUserAgent['name'];
462 $short = $osFromUserAgent['short_name'];
463 }
464 } elseif (!empty($osFromUserAgent['name'])) {
465 $name = $osFromUserAgent['name'];
466 $version = $osFromUserAgent['version'];
467 $short = $osFromUserAgent['short_name'];
468 } else {
469 return [];
470 }
471
472 $platform = $this->parsePlatform();
473 $family = self::getOsFamily($short);
474 $androidApps = [
475 'com.hisense.odinbrowser', 'com.seraphic.openinet.pre', 'com.appssppa.idesktoppcbrowser',
476 'every.browser.inc',
477 ];
478
479 if (null !== $this->clientHints) {
480 if (\in_array($this->clientHints->getApp(), $androidApps) && 'Android' !== $name) {
481 $name = 'Android';
482 $family = 'Android';
483 $short = 'ADR';
484 $version = '';
485 }
486
487 if ('org.lineageos.jelly' === $this->clientHints->getApp() && 'Lineage OS' !== $name) {
488 $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0');
489
490 $name = 'Lineage OS';
491 $family = 'Android';
492 $short = 'LEN';
493 $version = $this->lineageOsVersionMapping[$version]
494 ?? $this->lineageOsVersionMapping[$majorVersion] ?? '';
495 }
496
497 if ('org.mozilla.tv.firefox' === $this->clientHints->getApp() && 'Fire OS' !== $name) {
498 $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0');
499
500 $name = 'Fire OS';
501 $family = 'Android';
502 $short = 'FIR';
503 $version = $this->fireOsVersionMapping[$version] ?? $this->fireOsVersionMapping[$majorVersion] ?? '';
504 }
505 }
506
507 $return = [
508 'name' => $name,
509 'short_name' => $short,
510 'version' => $version,
511 'platform' => $platform,
512 'family' => $family,
513 ];
514
515 if (\in_array($return['name'], self::$operatingSystems)) {
516 $return['short_name'] = \array_search($return['name'], self::$operatingSystems);
517 }
518
519 return $return;
520 }
521
522 /**
523 * Returns the operating system family for the given operating system
524 *
525 * @param string $osLabel name or short name
526 *
527 * @return string|null If null, "Unknown"
528 */
529 public static function getOsFamily(string $osLabel): ?string
530 {
531 if (\in_array($osLabel, self::$operatingSystems)) {
532 $osLabel = \array_search($osLabel, self::$operatingSystems);
533 }
534
535 foreach (self::$osFamilies as $family => $labels) {
536 if (\in_array($osLabel, $labels)) {
537 return (string) $family;
538 }
539 }
540
541 return null;
542 }
543
544 /**
545 * Returns true if OS is desktop
546 *
547 * @param string $osName OS short name
548 *
549 * @return bool
550 */
551 public static function isDesktopOs(string $osName): bool
552 {
553 $osFamily = self::getOsFamily($osName);
554
555 return \in_array($osFamily, self::$desktopOsArray);
556 }
557
558 /**
559 * Returns the full name for the given short name
560 *
561 * @param string $os
562 * @param string|null $ver
563 *
564 * @return ?string
565 */
566 public static function getNameFromId(string $os, ?string $ver = null): ?string
567 {
568 if (\array_key_exists($os, self::$operatingSystems)) {
569 $osFullName = self::$operatingSystems[$os];
570
571 return \trim($osFullName . ' ' . $ver);
572 }
573
574 return null;
575 }
576
577 /**
578 * Returns the OS that can be safely detected from client hints
579 *
580 * @return array
581 */
582 protected function parseOsFromClientHints(): array
583 {
584 $name = $version = $short = '';
585
586 if ($this->clientHints instanceof ClientHints && $this->clientHints->getOperatingSystem()) {
587 $hintName = $this->applyClientHintMapping($this->clientHints->getOperatingSystem());
588
589 foreach (self::$operatingSystems as $osShort => $osName) {
590 if ($this->fuzzyCompare($hintName, $osName)) {
591 $name = $osName;
592 $short = $osShort;
593
594 break;
595 }
596 }
597
598 $version = $this->clientHints->getOperatingSystemVersion();
599
600 if ('Windows' === $name) {
601 $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0');
602 $minorVersion = (int) (\explode('.', $version, 2)[1] ?? '0');
603
604 if (0 === $majorVersion) {
605 $minorVersionMapping = [1 => '7', 2 => '8', 3 => '8.1'];
606 $version = $minorVersionMapping[$minorVersion] ?? $version;
607 } elseif ($majorVersion > 0 && $majorVersion < 11) {
608 $version = '10';
609 } elseif ($majorVersion > 10) {
610 $version = '11';
611 }
612 }
613
614 // On Windows, version 0.0.0 can be either 7, 8 or 8.1, so we return 0.0.0
615 if ('Windows' !== $name && '0.0.0' !== $version && 0 === (int) $version) {
616 $version = '';
617 }
618 }
619
620 return [
621 'name' => $name,
622 'short_name' => $short,
623 'version' => $this->buildVersion($version, []),
624 ];
625 }
626
627 /**
628 * Returns the OS that can be detected from useragent
629 *
630 * @return array
631 *
632 * @throws \Exception
633 */
634 protected function parseOsFromUserAgent(): array
635 {
636 $osRegex = $matches = [];
637 $name = $version = $short = '';
638
639 foreach ($this->getRegexes() as $osRegex) {
640 $matches = $this->matchUserAgent($osRegex['regex']);
641
642 if ($matches) {
643 break;
644 }
645 }
646
647 if (!empty($matches)) {
648 $name = $this->buildByMatch($osRegex['name'], $matches);
649 ['name' => $name, 'short' => $short] = self::getShortOsData($name);
650
651 $version = \array_key_exists('version', $osRegex)
652 ? $this->buildVersion((string) $osRegex['version'], $matches)
653 : '';
654
655 foreach ($osRegex['versions'] ?? [] as $regex) {
656 $matches = $this->matchUserAgent($regex['regex']);
657
658 if (!$matches) {
659 continue;
660 }
661
662 if (\array_key_exists('name', $regex)) {
663 $name = $this->buildByMatch($regex['name'], $matches);
664 ['name' => $name, 'short' => $short] = self::getShortOsData($name);
665 }
666
667 if (\array_key_exists('version', $regex)) {
668 $version = $this->buildVersion((string) $regex['version'], $matches);
669 }
670
671 break;
672 }
673 }
674
675 return [
676 'name' => $name,
677 'short_name' => $short,
678 'version' => $version,
679 ];
680 }
681
682 /**
683 * Parse current UserAgent string for the operating system platform
684 *
685 * @return string
686 */
687 protected function parsePlatform(): string
688 {
689 // Use architecture from client hints if available
690 if ($this->clientHints instanceof ClientHints && $this->clientHints->getArchitecture()) {
691 $arch = \strtolower($this->clientHints->getArchitecture());
692
693 if (false !== \strpos($arch, 'arm')) {
694 return 'ARM';
695 }
696
697 if (false !== \strpos($arch, 'loongarch64')) {
698 return 'LoongArch64';
699 }
700
701 if (false !== \strpos($arch, 'mips')) {
702 return 'MIPS';
703 }
704
705 if (false !== \strpos($arch, 'sh4')) {
706 return 'SuperH';
707 }
708
709 if (false !== \strpos($arch, 'sparc64')) {
710 return 'SPARC64';
711 }
712
713 if (false !== \strpos($arch, 'x64')
714 || (false !== \strpos($arch, 'x86') && '64' === $this->clientHints->getBitness())
715 ) {
716 return 'x64';
717 }
718
719 if (false !== \strpos($arch, 'x86')) {
720 return 'x86';
721 }
722 }
723
724 if ($this->matchUserAgent('arm[ _;)ev]|.*arm$|.*arm64|aarch64|Apple ?TV|Watch ?OS|Watch1,[12]')) {
725 return 'ARM';
726 }
727
728 if ($this->matchUserAgent('loongarch64')) {
729 return 'LoongArch64';
730 }
731
732 if ($this->matchUserAgent('mips')) {
733 return 'MIPS';
734 }
735
736 if ($this->matchUserAgent('sh4')) {
737 return 'SuperH';
738 }
739
740 if ($this->matchUserAgent('sparc64')) {
741 return 'SPARC64';
742 }
743
744 if ($this->matchUserAgent('64-?bit|WOW64|(?:Intel)?x64|WINDOWS_64|win64|.*amd64|.*x86_?64')) {
745 return 'x64';
746 }
747
748 if ($this->matchUserAgent('.*32bit|.*win32|(?:i[0-9]|x)86|i86pc')) {
749 return 'x86';
750 }
751
752 return '';
753 }
754 }
755