Client
1 month ago
Device
1 month ago
AbstractBotParser.php
2 years ago
AbstractParser.php
1 month ago
Bot.php
3 months ago
OperatingSystem.php
1 month ago
VendorFragment.php
2 years ago
OperatingSystem.php
379 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 | declare (strict_types=1); |
| 11 | namespace IAWPSCOPED\DeviceDetector\Parser; |
| 12 | |
| 13 | use IAWPSCOPED\DeviceDetector\ClientHints; |
| 14 | /** |
| 15 | * Class OperatingSystem |
| 16 | * |
| 17 | * Parses the useragent for operating system information |
| 18 | * |
| 19 | * Detected operating systems can be found in self::$operatingSystems and /regexes/oss.yml |
| 20 | * This class also defined some operating system families and methods to get the family for a specific os |
| 21 | * @internal |
| 22 | */ |
| 23 | class OperatingSystem extends AbstractParser |
| 24 | { |
| 25 | /** |
| 26 | * @var string |
| 27 | */ |
| 28 | protected $fixtureFile = 'regexes/oss.yml'; |
| 29 | /** |
| 30 | * @var string |
| 31 | */ |
| 32 | protected $parserName = 'os'; |
| 33 | /** |
| 34 | * Known operating systems mapped to their internal short codes |
| 35 | * |
| 36 | * @var array |
| 37 | */ |
| 38 | protected static $operatingSystems = ['AIX' => 'AIX', 'AND' => 'Android', 'ADR' => 'Android TV', 'ALP' => 'Alpine Linux', 'AMZ' => 'Amazon Linux', 'AMG' => 'AmigaOS', 'ARM' => 'Armadillo OS', 'ARO' => 'AROS', 'ATV' => 'tvOS', 'ARC' => 'ArcaOS', 'ARL' => 'Arch Linux', 'AOS' => 'AOSC OS', 'ASP' => 'ASPLinux', 'AZU' => 'Azure Linux', 'BTR' => 'BackTrack', 'SBA' => 'Bada', 'BYI' => 'Baidu Yi', 'BEO' => 'BeOS', 'BLB' => 'BlackBerry OS', 'QNX' => 'BlackBerry Tablet OS', 'PAN' => 'blackPanther OS', 'BOS' => 'Bliss OS', 'BMP' => 'Brew', 'BSN' => 'BrightSignOS', 'BS1' => 'BSD', 'CAI' => 'Caixa Mágica', 'CES' => 'CentOS', 'CST' => 'CentOS Stream', 'CLO' => 'Clear Linux OS', 'CLR' => 'ClearOS Mobile', 'COS' => 'Chrome OS', 'CRS' => 'Chromium OS', 'CHN' => 'China OS', 'CON' => 'Contiki', 'COL' => 'Coolita OS', 'CYN' => 'CyanogenMod', 'DEB' => 'Debian', 'DEE' => 'Deepin', 'DFB' => 'DragonFly', 'DVK' => 'DVKBuntu', 'ELE' => 'ElectroBSD', 'ELM' => 'elementary OS', 'EUL' => 'EulerOS', 'FED' => 'Fedora', 'FEN' => 'Fenix', 'FOS' => 'Firefox OS', 'FIR' => 'Fire OS', 'FOR' => 'Foresight Linux', 'FRE' => 'Freebox', 'BSD' => 'FreeBSD', 'FRI' => 'FRITZ!OS', 'FYD' => 'FydeOS', 'FUC' => 'Fuchsia', 'GNT' => 'Gentoo', 'GNX' => 'GENIX', 'GEO' => 'GEOS', 'GHO' => 'GhostBSD', 'GNS' => 'gNewSense', 'GRI' => 'GridOS', 'GTV' => 'Google TV', 'HPX' => 'HP-UX', 'HAI' => 'Haiku OS', 'IPA' => 'iPadOS', 'HAR' => 'HarmonyOS', 'HNX' => 'HarmonyOS NEXT', 'HAS' => 'HasCodingOS', 'HEL' => 'HELIX OS', 'IRI' => 'IRIX', 'INF' => 'Inferno', 'JME' => 'Java ME', 'JOL' => 'Joli OS', 'KOS' => 'KaiOS', 'KAL' => 'Kali', 'KAN' => 'Kanotix', 'KIN' => 'KIN OS', 'KOL' => 'KolibriOS', 'KNO' => 'Knoppix', 'KTV' => 'KreaTV', 'KBT' => 'Kubuntu', 'LIN' => 'GNU/Linux', 'LEA' => 'LeafOS', 'LND' => 'LindowsOS', 'LNS' => 'Linspire', 'LEN' => 'Lineage OS', 'LPU' => 'Linpus', 'LIR' => 'Liri OS', 'LOO' => 'Loongnix', 'LBT' => 'Lubuntu', 'LOS' => 'Lumin OS', 'LUN' => 'LuneOS', 'VLN' => 'VectorLinux', 'MAC' => 'Mac', 'MAE' => 'Maemo', 'MAG' => 'Mageia', 'MDR' => 'Mandriva', 'SMG' => 'MeeGo', 'MET' => 'Meta Horizon', 'MNX' => 'MINIX', 'MOS' => 'Mocor OS', 'MCD' => 'MocorDroid', 'MON' => 'moonOS', 'EZX' => 'Motorola EZX', 'MIN' => 'Mint', 'MLD' => 'MildWild', 'MOR' => 'MorphOS', 'NBS' => 'NetBSD', 'MTK' => 'MTK / Nucleus', 'MRE' => 'MRE', 'NXT' => 'NeXTSTEP', 'NWS' => 'NEWS-OS', 'WII' => 'Nintendo', 'NDS' => 'Nintendo Mobile', 'NOV' => 'Nova', 'NTX' => 'NuttX', 'OS2' => 'OS/2', 'T64' => 'OSF1', 'OSS' => 'openSUSE', 'OBS' => 'OpenBSD', 'OHS' => 'OpenHarmony', 'OVS' => 'OpenVMS', 'OVZ' => 'OpenVZ', 'OWR' => 'OpenWrt', 'OTV' => 'Opera TV', 'ORA' => 'Oracle Linux', 'ORD' => 'Ordissimo', 'ORS' => 'Orsay', 'PAR' => 'Pardus', 'PCL' => 'PCLinuxOS', 'PIC' => 'PICO OS', 'PLN' => 'Plan 9', 'PLA' => 'Plasma Mobile', 'PSP' => 'PlayStation Portable', 'PS3' => 'PlayStation', 'PVE' => 'Proxmox VE', 'PUF' => 'Puffin OS', 'PUR' => 'PureOS', 'QTP' => 'Qtopia', 'PIO' => 'Raspberry Pi OS', 'RAS' => 'Raspbian', 'RHT' => 'Red Hat', 'RST' => 'Red Star', 'RED' => 'RedOS', 'REV' => 'Revenge OS', 'RIS' => 'risingOS', 'ROS' => 'RISC OS', 'ROC' => 'Rocky Linux', 'ROK' => 'Roku OS', 'RSO' => 'Rosa', 'ROU' => 'RouterOS', 'REM' => 'Remix OS', 'RRS' => 'Resurrection Remix OS', 'REX' => 'REX', 'RZD' => 'RazoDroiD', 'RXT' => 'RTOS & Next', 'SAB' => 'Sabayon', 'SSE' => 'SUSE', 'SAF' => 'Sailfish OS', 'SCI' => 'Scientific Linux', 'SEE' => 'SeewoOS', 'SER' => 'SerenityOS', 'SIR' => 'Sirin OS', 'SLW' => 'Slackware', 'SMA' => 'Smartisan OS', 'SOS' => 'Solaris', 'SBL' => 'Star-Blade OS', 'SYL' => 'Syllable', 'SYM' => 'Symbian', 'SYS' => 'Symbian OS', 'S40' => 'Symbian OS Series 40', 'S60' => 'Symbian OS Series 60', 'SY3' => 'Symbian^3', 'TEN' => 'TencentOS', 'TDX' => 'ThreadX', 'TIT' => 'Titan OS', 'TIZ' => 'Tizen', 'TIV' => 'TiVo OS', 'TOS' => 'TmaxOS', 'TUR' => 'Turbolinux', 'UBT' => 'Ubuntu', 'ULT' => 'ULTRIX', 'UOS' => 'UOS', 'VID' => 'VIDAA', 'VIZ' => 'ViziOS', 'WAS' => 'watchOS', 'WER' => 'Wear OS', 'WTV' => 'WebTV', 'WHS' => 'Whale OS', 'WIN' => 'Windows', 'WCE' => 'Windows CE', 'WIO' => 'Windows IoT', 'WMO' => 'Windows Mobile', 'WPH' => 'Windows Phone', 'WRT' => 'Windows RT', 'WPO' => 'WoPhone', 'XBX' => 'Xbox', 'XBT' => 'Xubuntu', 'YNS' => 'YunOS', 'ZEN' => 'Zenwalk', 'ZOR' => 'ZorinOS', 'IOS' => 'iOS', 'POS' => 'palmOS', 'WEB' => 'Webian', 'WOS' => 'webOS']; |
| 39 | /** |
| 40 | * Operating system families mapped to the short codes of the associated operating systems |
| 41 | * |
| 42 | * @var array |
| 43 | */ |
| 44 | protected static $osFamilies = ['Android' => ['AND', 'CYN', 'FIR', 'REM', 'RZD', 'MLD', 'MCD', 'YNS', 'GRI', 'HAR', 'ADR', 'CLR', 'BOS', 'REV', 'LEN', 'SIR', 'RRS', 'WER', 'PIC', 'ARM', 'HEL', 'BYI', 'RIS', 'PUF', 'LEA', 'MET', 'SMA'], 'AmigaOS' => ['AMG', 'MOR', 'ARO'], 'BlackBerry' => ['BLB', 'QNX'], 'Brew' => ['BMP'], 'BeOS' => ['BEO', 'HAI'], 'Chrome OS' => ['COS', 'CRS', 'FYD', 'SEE'], 'Firefox OS' => ['FOS', 'KOS'], 'Gaming Console' => ['WII', 'PS3'], 'Google TV' => ['GTV'], 'IBM' => ['OS2', 'ARC'], 'iOS' => ['IOS', 'ATV', 'WAS', 'IPA'], 'RISC OS' => ['ROS'], 'GNU/Linux' => ['LIN', 'ARL', 'DEB', 'KNO', 'MIN', 'UBT', 'KBT', 'XBT', 'LBT', 'FED', 'RHT', 'VLN', 'MDR', 'GNT', 'SAB', 'SLW', 'SSE', 'CES', 'BTR', 'SAF', 'ORD', 'TOS', 'RSO', 'DEE', 'FRE', 'MAG', 'FEN', 'CAI', 'PCL', 'HAS', 'LOS', 'DVK', 'ROK', 'OWR', 'OTV', 'KTV', 'PUR', 'PLA', 'FUC', 'PAR', 'FOR', 'MON', 'KAN', 'ZEN', 'LND', 'LNS', 'CHN', 'AMZ', 'TEN', 'CST', 'NOV', 'ROU', 'ZOR', 'RED', 'KAL', 'ORA', 'VID', 'TIV', 'BSN', 'RAS', 'UOS', 'PIO', 'FRI', 'LIR', 'WEB', 'SER', 'ASP', 'AOS', 'LOO', 'EUL', 'SCI', 'ALP', 'CLO', 'ROC', 'OVZ', 'PVE', 'RST', 'EZX', 'GNS', 'JOL', 'TUR', 'QTP', 'WPO', 'PAN', 'VIZ', 'AZU', 'COL', 'OSS', 'ELM', 'LPU'], 'Mac' => ['MAC'], 'Mobile Gaming Console' => ['PSP', 'NDS', 'XBX'], 'OpenHarmony' => ['OHS', 'HNX'], 'OpenVMS' => ['OVS'], 'Real-time OS' => ['MTK', 'TDX', 'MRE', 'JME', 'REX', 'RXT', 'KOL', 'MOS', 'NTX'], 'Other Mobile' => ['WOS', 'POS', 'SBA', 'TIZ', 'SMG', 'MAE', 'LUN', 'GEO', 'CON'], 'Symbian' => ['SYM', 'SYS', 'SY3', 'S60', 'S40'], 'Unix' => ['SOS', 'AIX', 'HPX', 'BSD', 'NBS', 'OBS', 'DFB', 'SYL', 'IRI', 'T64', 'INF', 'ELE', 'GNX', 'ULT', 'NWS', 'NXT', 'SBL', 'BS1', 'GHO', 'PLN', 'MNX'], 'WebTV' => ['WTV'], 'Windows' => ['WIN'], 'Windows Mobile' => ['WPH', 'WMO', 'WCE', 'WRT', 'WIO', 'KIN'], 'Other Smart TV' => ['WHS', 'TIT', 'ORS']]; |
| 45 | /** |
| 46 | * Contains a list of mappings from OS names we use to known client hint values |
| 47 | * |
| 48 | * @var array<string, array<string>> |
| 49 | */ |
| 50 | protected static $clientHintMapping = ['GNU/Linux' => ['Linux'], 'Mac' => ['MacOS']]; |
| 51 | /** |
| 52 | * Operating system families that are known as desktop only |
| 53 | * |
| 54 | * @var array |
| 55 | */ |
| 56 | protected static $desktopOsArray = ['AmigaOS', 'IBM', 'GNU/Linux', 'Mac', 'Unix', 'Windows', 'BeOS', 'Chrome OS', 'OpenVMS']; |
| 57 | /** |
| 58 | * Fire OS version mapping |
| 59 | * |
| 60 | * @var array |
| 61 | */ |
| 62 | private $fireOsVersionMapping = ['14' => '14', '13' => '14', '12' => '14', '11' => '8', '10' => '8', '9' => '7', '7' => '6', '5' => '5', '4.4.3' => '4.5.1', '4.4.2' => '4', '4.2.2' => '3', '4.0.3' => '3', '4.0.2' => '3', '4' => '2', '2' => '1']; |
| 63 | /** |
| 64 | * Lineage OS version mapping |
| 65 | * |
| 66 | * @var array |
| 67 | */ |
| 68 | private $lineageOsVersionMapping = ['16' => '23', '15' => '22', '14' => '21', '13' => '20.0', '12.1' => '19.1', '12' => '19.0', '11' => '18.0', '10' => '17.0', '9' => '16.0', '8.1.0' => '15.1', '8.0.0' => '15.0', '7.1.2' => '14.1', '7.1.1' => '14.1', '7.0' => '14.0', '6.0.1' => '13.0', '6.0' => '13.0', '5.1.1' => '12.1', '5.0.2' => '12.0', '5.0' => '12.0', '4.4.4' => '11.0', '4.3' => '10.2', '4.2.2' => '10.1', '4.0.4' => '9.1.0']; |
| 69 | /** |
| 70 | * Returns all available operating systems |
| 71 | * |
| 72 | * @return array |
| 73 | */ |
| 74 | public static function getAvailableOperatingSystems() : array |
| 75 | { |
| 76 | return self::$operatingSystems; |
| 77 | } |
| 78 | /** |
| 79 | * Returns all available operating system families |
| 80 | * |
| 81 | * @return array |
| 82 | */ |
| 83 | public static function getAvailableOperatingSystemFamilies() : array |
| 84 | { |
| 85 | return self::$osFamilies; |
| 86 | } |
| 87 | /** |
| 88 | * Returns the os name and shot name |
| 89 | * |
| 90 | * @param string $name |
| 91 | * |
| 92 | * @return array |
| 93 | */ |
| 94 | public static function getShortOsData(string $name) : array |
| 95 | { |
| 96 | $short = 'UNK'; |
| 97 | foreach (self::$operatingSystems as $osShort => $osName) { |
| 98 | if (\strtolower($name) !== \strtolower($osName)) { |
| 99 | continue; |
| 100 | } |
| 101 | $name = $osName; |
| 102 | $short = $osShort; |
| 103 | break; |
| 104 | } |
| 105 | return \compact('short', 'name'); |
| 106 | } |
| 107 | /** |
| 108 | * @inheritdoc |
| 109 | */ |
| 110 | public function parse() : ?array |
| 111 | { |
| 112 | $this->restoreUserAgentFromClientHints(); |
| 113 | $osFromClientHints = $this->parseOsFromClientHints(); |
| 114 | $osFromUserAgent = $this->parseOsFromUserAgent(); |
| 115 | if (!empty($osFromClientHints['name'])) { |
| 116 | $name = $osFromClientHints['name']; |
| 117 | $version = $osFromClientHints['version']; |
| 118 | // use version from user agent if non was provided in client hints, but os family from useragent matches |
| 119 | if (empty($version) && self::getOsFamily($name) === self::getOsFamily($osFromUserAgent['name'])) { |
| 120 | $version = $osFromUserAgent['version']; |
| 121 | } |
| 122 | // On Windows, version 0.0.0 can be either 7, 8 or 8.1 |
| 123 | if ('Windows' === $name && '0.0.0' === $version) { |
| 124 | $version = '10' === $osFromUserAgent['version'] ? '' : $osFromUserAgent['version']; |
| 125 | } |
| 126 | // If the OS name detected from client hints matches the OS family from user agent |
| 127 | // but the os name is another, we use the one from user agent, as it might be more detailed |
| 128 | if ($osFromUserAgent['name'] !== $name && self::getOsFamily($osFromUserAgent['name']) === $name) { |
| 129 | $name = $osFromUserAgent['name']; |
| 130 | if ('LeafOS' === $name || 'HarmonyOS' === $name) { |
| 131 | $version = ''; |
| 132 | } |
| 133 | if ('PICO OS' === $name) { |
| 134 | $version = $osFromUserAgent['version']; |
| 135 | } |
| 136 | if ('Fire OS' === $name && !empty($osFromClientHints['version'])) { |
| 137 | $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0'); |
| 138 | $version = $this->fireOsVersionMapping[$version] ?? $this->fireOsVersionMapping[$majorVersion] ?? ''; |
| 139 | } |
| 140 | } |
| 141 | $short = $osFromClientHints['short_name']; |
| 142 | // Chrome OS is in some cases reported as Linux in client hints, we fix this only if the version matches |
| 143 | if ('GNU/Linux' === $name && 'Chrome OS' === $osFromUserAgent['name'] && $osFromClientHints['version'] === $osFromUserAgent['version']) { |
| 144 | $name = $osFromUserAgent['name']; |
| 145 | $short = $osFromUserAgent['short_name']; |
| 146 | } |
| 147 | // Chrome OS is in some cases reported as Android in client hints |
| 148 | if ('Android' === $name && 'Chrome OS' === $osFromUserAgent['name']) { |
| 149 | $name = $osFromUserAgent['name']; |
| 150 | $version = ''; |
| 151 | $short = $osFromUserAgent['short_name']; |
| 152 | } |
| 153 | // Meta Horizon is reported as Linux in client hints |
| 154 | if ('GNU/Linux' === $name && 'Meta Horizon' === $osFromUserAgent['name']) { |
| 155 | $name = $osFromUserAgent['name']; |
| 156 | $short = $osFromUserAgent['short_name']; |
| 157 | } |
| 158 | } elseif (!empty($osFromUserAgent['name'])) { |
| 159 | $name = $osFromUserAgent['name']; |
| 160 | $version = $osFromUserAgent['version']; |
| 161 | $short = $osFromUserAgent['short_name']; |
| 162 | } else { |
| 163 | return []; |
| 164 | } |
| 165 | $platform = $this->parsePlatform(); |
| 166 | $family = self::getOsFamily($short); |
| 167 | $androidApps = ['com.hisense.odinbrowser', 'com.seraphic.openinet.pre', 'com.appssppa.idesktoppcbrowser', 'every.browser.inc']; |
| 168 | if (null !== $this->clientHints) { |
| 169 | if ('Android' !== $name && \in_array($this->clientHints->getApp(), $androidApps)) { |
| 170 | $name = 'Android'; |
| 171 | $family = 'Android'; |
| 172 | $short = 'ADR'; |
| 173 | $version = ''; |
| 174 | } |
| 175 | if ('Lineage OS' !== $name && 'org.lineageos.jelly' === $this->clientHints->getApp()) { |
| 176 | $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0'); |
| 177 | $name = 'Lineage OS'; |
| 178 | $family = 'Android'; |
| 179 | $short = 'LEN'; |
| 180 | $version = $this->lineageOsVersionMapping[$version] ?? $this->lineageOsVersionMapping[$majorVersion] ?? ''; |
| 181 | } |
| 182 | if ('Fire OS' !== $name && 'org.mozilla.tv.firefox' === $this->clientHints->getApp()) { |
| 183 | $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0'); |
| 184 | $name = 'Fire OS'; |
| 185 | $family = 'Android'; |
| 186 | $short = 'FIR'; |
| 187 | $version = $this->fireOsVersionMapping[$version] ?? $this->fireOsVersionMapping[$majorVersion] ?? ''; |
| 188 | } |
| 189 | } |
| 190 | if ('' !== $name && !\in_array($name, self::$operatingSystems)) { |
| 191 | // This Exception should never be thrown. If so, a defined operating system is missing in $operatingSystems |
| 192 | throw new \Exception(\sprintf("The operating system '%s' should be listed in operatingSystems array. Tried to parse user agent: %s", $name, $this->userAgent)); |
| 193 | // @codeCoverageIgnore |
| 194 | } |
| 195 | $return = ['name' => $name, 'short_name' => $short, 'version' => $version, 'platform' => $platform, 'family' => $family]; |
| 196 | if (\in_array($return['name'], self::$operatingSystems, \true)) { |
| 197 | $return['short_name'] = \array_search($return['name'], self::$operatingSystems, \true); |
| 198 | } |
| 199 | return $return; |
| 200 | } |
| 201 | /** |
| 202 | * Returns the operating system family for the given operating system |
| 203 | * |
| 204 | * @param string $osLabel name or short name |
| 205 | * |
| 206 | * @return string|null If null, "Unknown" |
| 207 | */ |
| 208 | public static function getOsFamily(string $osLabel) : ?string |
| 209 | { |
| 210 | if (\in_array($osLabel, self::$operatingSystems, \true)) { |
| 211 | $osLabel = (string) \array_search($osLabel, self::$operatingSystems, \true); |
| 212 | } |
| 213 | foreach (self::$osFamilies as $family => $labels) { |
| 214 | if (\in_array($osLabel, $labels, \true)) { |
| 215 | return $family; |
| 216 | } |
| 217 | } |
| 218 | return null; |
| 219 | } |
| 220 | /** |
| 221 | * Returns true if OS is desktop |
| 222 | * |
| 223 | * @param string $osName OS short name |
| 224 | * |
| 225 | * @return bool |
| 226 | */ |
| 227 | public static function isDesktopOs(string $osName) : bool |
| 228 | { |
| 229 | $osFamily = self::getOsFamily($osName); |
| 230 | return \in_array($osFamily, self::$desktopOsArray, \true); |
| 231 | } |
| 232 | /** |
| 233 | * Returns the full name for the given short name |
| 234 | * |
| 235 | * @param string $os |
| 236 | * @param string|null $ver |
| 237 | * |
| 238 | * @return ?string |
| 239 | */ |
| 240 | public static function getNameFromId(string $os, ?string $ver = null) : ?string |
| 241 | { |
| 242 | if (\array_key_exists($os, self::$operatingSystems)) { |
| 243 | $osFullName = self::$operatingSystems[$os]; |
| 244 | return \trim($osFullName . ' ' . $ver); |
| 245 | } |
| 246 | return null; |
| 247 | } |
| 248 | /** |
| 249 | * Returns the OS that can be safely detected from client hints |
| 250 | * |
| 251 | * @return array |
| 252 | */ |
| 253 | protected function parseOsFromClientHints() : array |
| 254 | { |
| 255 | $name = $version = $short = ''; |
| 256 | if ($this->clientHints instanceof ClientHints && $this->clientHints->getOperatingSystem()) { |
| 257 | $hintName = $this->applyClientHintMapping($this->clientHints->getOperatingSystem()); |
| 258 | foreach (self::$operatingSystems as $osShort => $osName) { |
| 259 | if ($this->fuzzyCompare($hintName, $osName)) { |
| 260 | $name = $osName; |
| 261 | $short = $osShort; |
| 262 | break; |
| 263 | } |
| 264 | } |
| 265 | $version = $this->clientHints->getOperatingSystemVersion(); |
| 266 | if ('Windows' === $name) { |
| 267 | $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0'); |
| 268 | $minorVersion = (int) (\explode('.', $version, 2)[1] ?? '0'); |
| 269 | if (0 === $majorVersion) { |
| 270 | $minorVersionMapping = [1 => '7', 2 => '8', 3 => '8.1']; |
| 271 | $version = $minorVersionMapping[$minorVersion] ?? $version; |
| 272 | } elseif ($majorVersion > 0 && $majorVersion < 11) { |
| 273 | $version = '10'; |
| 274 | } elseif ($majorVersion > 10) { |
| 275 | $version = '11'; |
| 276 | } |
| 277 | } |
| 278 | // On Windows, version 0.0.0 can be either 7, 8 or 8.1, so we return 0.0.0 |
| 279 | if ('Windows' !== $name && '0.0.0' !== $version && 0 === (int) $version) { |
| 280 | $version = ''; |
| 281 | } |
| 282 | } |
| 283 | return ['name' => $name, 'short_name' => $short, 'version' => $this->buildVersion($version, [])]; |
| 284 | } |
| 285 | /** |
| 286 | * Returns the OS that can be detected from useragent |
| 287 | * |
| 288 | * @return array |
| 289 | */ |
| 290 | protected function parseOsFromUserAgent() : array |
| 291 | { |
| 292 | $osRegex = $matches = []; |
| 293 | $name = $version = $short = ''; |
| 294 | foreach ($this->getRegexes() as $osRegex) { |
| 295 | $matches = $this->matchUserAgent($osRegex['regex']); |
| 296 | if ($matches) { |
| 297 | break; |
| 298 | } |
| 299 | } |
| 300 | if (!empty($matches)) { |
| 301 | $name = $this->buildByMatch($osRegex['name'], $matches); |
| 302 | ['name' => $name, 'short' => $short] = self::getShortOsData($name); |
| 303 | $version = \array_key_exists('version', $osRegex) ? $this->buildVersion((string) $osRegex['version'], $matches) : ''; |
| 304 | foreach ($osRegex['versions'] ?? [] as $regex) { |
| 305 | $matches = $this->matchUserAgent($regex['regex']); |
| 306 | if (!$matches) { |
| 307 | continue; |
| 308 | } |
| 309 | if (\array_key_exists('name', $regex)) { |
| 310 | $name = $this->buildByMatch($regex['name'], $matches); |
| 311 | ['name' => $name, 'short' => $short] = self::getShortOsData($name); |
| 312 | } |
| 313 | if (\array_key_exists('version', $regex)) { |
| 314 | $version = $this->buildVersion((string) $regex['version'], $matches); |
| 315 | } |
| 316 | break; |
| 317 | } |
| 318 | } |
| 319 | return ['name' => $name, 'short_name' => $short, 'version' => $version]; |
| 320 | } |
| 321 | /** |
| 322 | * Parse current UserAgent string for the operating system platform |
| 323 | * |
| 324 | * @return string |
| 325 | * |
| 326 | * @throws \Exception |
| 327 | */ |
| 328 | protected function parsePlatform() : string |
| 329 | { |
| 330 | // Use architecture from client hints if available |
| 331 | if ($this->clientHints instanceof ClientHints && $this->clientHints->getArchitecture()) { |
| 332 | $arch = \strtolower($this->clientHints->getArchitecture()); |
| 333 | if (\false !== \strpos($arch, 'arm')) { |
| 334 | return 'ARM'; |
| 335 | } |
| 336 | if (\false !== \strpos($arch, 'loongarch64')) { |
| 337 | return 'LoongArch64'; |
| 338 | } |
| 339 | if (\false !== \strpos($arch, 'mips')) { |
| 340 | return 'MIPS'; |
| 341 | } |
| 342 | if (\false !== \strpos($arch, 'sh4')) { |
| 343 | return 'SuperH'; |
| 344 | } |
| 345 | if (\false !== \strpos($arch, 'sparc64')) { |
| 346 | return 'SPARC64'; |
| 347 | } |
| 348 | if (\false !== \strpos($arch, 'x64') || \false !== \strpos($arch, 'x86') && '64' === $this->clientHints->getBitness()) { |
| 349 | return 'x64'; |
| 350 | } |
| 351 | if (\false !== \strpos($arch, 'x86')) { |
| 352 | return 'x86'; |
| 353 | } |
| 354 | } |
| 355 | if ($this->matchUserAgent('arm[ _;)ev]|.*arm$|.*arm64|aarch64|Apple ?TV|Watch ?OS|Watch1,[12]')) { |
| 356 | return 'ARM'; |
| 357 | } |
| 358 | if ($this->matchUserAgent('loongarch64')) { |
| 359 | return 'LoongArch64'; |
| 360 | } |
| 361 | if ($this->matchUserAgent('mips')) { |
| 362 | return 'MIPS'; |
| 363 | } |
| 364 | if ($this->matchUserAgent('sh4')) { |
| 365 | return 'SuperH'; |
| 366 | } |
| 367 | if ($this->matchUserAgent('sparc64')) { |
| 368 | return 'SPARC64'; |
| 369 | } |
| 370 | if ($this->matchUserAgent('64-?bit|WOW64|(?:Intel)?x64|WINDOWS_64|win64|IRIX;?64|.*amd64|.*x86_?64')) { |
| 371 | return 'x64'; |
| 372 | } |
| 373 | if ($this->matchUserAgent('.*32bit|.*win32|(?:i[0-9]|x)86|i86pc')) { |
| 374 | return 'x86'; |
| 375 | } |
| 376 | return ''; |
| 377 | } |
| 378 | } |
| 379 |