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 |