'10', '6.3' => '8.1', '6.2' => '8', '6.1' => '7', '6.0' => 'Vista', '5.1' => 'XP', ]; /** * Parse a user agent string. * * @param string $sUserAgent the User-Agent header * @return array with keys browser, browser_version, os, os_version, device and bot */ public static function parse($sUserAgent) { $sUserAgent = trim((string)$sUserAgent); $aResult = [ 'browser' => '', 'browser_version' => '', 'os' => '', 'os_version' => '', 'device' => self::DEVICE_DESKTOP, 'bot' => false, ]; if($sUserAgent === '' || self::isBot($sUserAgent)) { $aResult['device'] = self::DEVICE_BOT; $aResult['bot'] = true; return $aResult; } foreach(self::$_aBrowsers as list($sName, $sPattern)) if(preg_match($sPattern, $sUserAgent, $aMatch)) { $aResult['browser'] = $sName; $aResult['browser_version'] = self::_majorMinor($aMatch[1]); break; } foreach(self::$_aSystems as list($sName, $sPattern)) if(preg_match($sPattern, $sUserAgent, $aMatch)) { $aResult['os'] = $sName; $aResult['os_version'] = self::_systemVersion($sName, isset($aMatch[1]) ? $aMatch[1] : ''); break; } $aResult['device'] = self::_device($sUserAgent, $aResult['os']); return $aResult; } /** * Whether a user agent belongs to a crawler, a link preview, a monitor or an HTTP library. */ public static function isBot($sUserAgent) { $sUserAgent = trim((string)$sUserAgent); if($sUserAgent === '') return true; return (bool)preg_match(self::$_sBotsPattern, $sUserAgent); } /** * Name and category of a bot's user agent, or null for a browser. * * @return array|null ['name' => ..., 'category' => ...] */ public static function getBot($sUserAgent) { $sUserAgent = trim((string)$sUserAgent); if($sUserAgent === '') return ['name' => 'Unknown', 'category' => self::BOT_SCRIPT]; foreach(self::$_aBots as list($sPattern, $sName, $sCategory)) if(preg_match($sPattern, $sUserAgent)) return ['name' => $sName, 'category' => $sCategory]; if(!self::isBot($sUserAgent)) return null; // an unlisted bot: its own name when the user agent carries one ("ExampleBot/1.0") if(preg_match('/([A-Za-z][A-Za-z0-9._-]{1,40}(?:bot|crawler|spider))\b/i', $sUserAgent, $aMatch)) return ['name' => get_mb_substr($aMatch[1], 0, 64), 'category' => self::BOT_OTHER]; return ['name' => 'Other', 'category' => self::BOT_OTHER]; } protected static function _device($sUserAgent, $sOs) { if(preg_match('/\b(?:SmartTV|SMART-TV|Smart TV|AppleTV|Apple TV|GoogleTV|Android TV|BRAVIA|HbbTV|NetCast|Web0S|webOS TV|Tizen.*TV|CrKey|AFT[A-Z]+|Roku)\b/i', $sUserAgent)) return self::DEVICE_TV; if(preg_match('/\b(?:PlayStation|Xbox|Nintendo)\b/i', $sUserAgent)) return self::DEVICE_CONSOLE; if(preg_match('/\b(?:iPad|Tablet|Kindle|Silk|PlayBook)\b/i', $sUserAgent) || $sOs === 'iPadOS') return self::DEVICE_TABLET; if($sOs === 'Android' && stripos($sUserAgent, 'Mobile') === false) return self::DEVICE_TABLET; if(preg_match('/Mobi|iPhone|iPod|Windows Phone|Opera Mini|BlackBerry|BB10|KAIOS/i', $sUserAgent) || in_array($sOs, ['iOS', 'Android', 'HarmonyOS'], true)) return self::DEVICE_MOBILE; return self::DEVICE_DESKTOP; } protected static function _systemVersion($sOs, $sVersion) { $sVersion = str_replace('_', '.', $sVersion); if($sOs === 'Windows') return isset(self::$_aWindowsVersions[$sVersion]) ? self::$_aWindowsVersions[$sVersion] : ''; // Safari has reported macOS as 10.15.7 since Big Sur, so the minor version means nothing. if($sOs === 'macOS' && bx_mb_strpos($sVersion, '10.15') === 0) return ''; return self::_majorMinor($sVersion); } protected static function _majorMinor($sVersion) { $aParts = explode('.', (string)$sVersion); if($aParts[0] === '') return ''; return isset($aParts[1]) && $aParts[1] !== '' && $aParts[1] !== '0' ? $aParts[0] . '.' . $aParts[1] : $aParts[0]; } } /** @} */