967 lines
31 KiB
PHP
967 lines
31 KiB
PHP
<?php
|
|
/**
|
|
* Piwik - Open source web analytics
|
|
*
|
|
* @link http://piwik.org
|
|
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
|
*
|
|
*/
|
|
|
|
class DeviceDetector
|
|
{
|
|
public static $deviceTypes = array(
|
|
'desktop', // 0
|
|
'smartphone', // 1
|
|
'tablet', // 2
|
|
'feature phone', // 3
|
|
'console', // 4
|
|
'tv', // 5
|
|
'car browser', // 6
|
|
'smart display', // 7
|
|
'camera' // 8
|
|
);
|
|
|
|
public static $deviceBrands = array(
|
|
'AC' => 'Acer',
|
|
'AI' => 'Airness',
|
|
'AL' => 'Alcatel',
|
|
'AN' => 'Arnova',
|
|
'AO' => 'Amoi',
|
|
'AP' => 'Apple',
|
|
'AR' => 'Archos',
|
|
'AU' => 'Asus',
|
|
'AV' => 'Avvio',
|
|
'AX' => 'Audiovox',
|
|
'BB' => 'BBK',
|
|
'BE' => 'Becker',
|
|
'BI' => 'Bird',
|
|
'BL' => 'Beetel',
|
|
'BO' => 'BangOlufsen',
|
|
'BQ' => 'BenQ',
|
|
'BS' => 'BenQ-Siemens',
|
|
'BX' => 'bq',
|
|
'CA' => 'Cat',
|
|
'CK' => 'Cricket',
|
|
'CL' => 'Compal',
|
|
'CN' => 'CnM',
|
|
'CR' => 'CreNova',
|
|
'CT' => 'Capitel',
|
|
'CO' => 'Coolpad',
|
|
'CU' => 'Cube',
|
|
'DE' => 'Denver',
|
|
'DB' => 'Dbtel',
|
|
'DC' => 'DoCoMo',
|
|
'DI' => 'Dicam',
|
|
'DL' => 'Dell',
|
|
'DM' => 'DMM',
|
|
'DP' => 'Dopod',
|
|
'EC' => 'Ericsson',
|
|
'EI' => 'Ezio',
|
|
'ER' => 'Ericy',
|
|
'ET' => 'eTouch',
|
|
'EZ' => 'Ezze',
|
|
'FL' => 'Fly',
|
|
'GD' => 'Gemini',
|
|
'GI' => 'Gionee',
|
|
'GG' => 'Gigabyte',
|
|
'GO' => 'Google',
|
|
'GR' => 'Gradiente',
|
|
'GU' => 'Grundig',
|
|
'HA' => 'Haier',
|
|
'HP' => 'HP',
|
|
'HT' => 'HTC',
|
|
'HU' => 'Huawei',
|
|
'HX' => 'Humax',
|
|
'IA' => 'Ikea',
|
|
'IK' => 'iKoMo',
|
|
'IM' => 'i-mate',
|
|
'IN' => 'Innostream',
|
|
'IX' => 'Intex',
|
|
'IO' => 'i-mobile',
|
|
'IQ' => 'INQ',
|
|
'IT' => 'Intek',
|
|
'IV' => 'Inverto',
|
|
'JI' => 'Jiayu',
|
|
'JO' => 'Jolla',
|
|
'KA' => 'Karbonn',
|
|
'KD' => 'KDDI',
|
|
'KN' => 'Kindle',
|
|
'KO' => 'Konka',
|
|
'KT' => 'K-Touch',
|
|
'KH' => 'KT-Tech',
|
|
'KY' => 'Kyocera',
|
|
'LA' => 'Lanix',
|
|
'LC' => 'LCT',
|
|
'LE' => 'Lenovo',
|
|
'LG' => 'LG',
|
|
'LO' => 'Loewe',
|
|
'LU' => 'LGUPlus',
|
|
'MA' => 'Manta Multimedia',
|
|
'MD' => 'Medion',
|
|
'ME' => 'Metz',
|
|
'MI' => 'MicroMax',
|
|
'MK' => 'MediaTek',
|
|
'MO' => 'Mio',
|
|
'MR' => 'Motorola',
|
|
'MS' => 'Microsoft',
|
|
'MT' => 'Mitsubishi',
|
|
'MY' => 'MyPhone',
|
|
'NE' => 'NEC',
|
|
'NG' => 'NGM',
|
|
'NI' => 'Nintendo',
|
|
'NK' => 'Nokia',
|
|
'NN' => 'Nikon',
|
|
'NW' => 'Newgen',
|
|
'NX' => 'Nexian',
|
|
'OD' => 'Onda',
|
|
'OP' => 'OPPO',
|
|
'OR' => 'Orange',
|
|
'OT' => 'O2',
|
|
'OU' => 'OUYA',
|
|
'PA' => 'Panasonic',
|
|
'PE' => 'PEAQ',
|
|
'PH' => 'Philips',
|
|
'PL' => 'Polaroid',
|
|
'PM' => 'Palm',
|
|
'PO' => 'phoneOne',
|
|
'PT' => 'Pantech',
|
|
'PP' => 'PolyPad',
|
|
'PR' => 'Prestigio',
|
|
'QT' => 'Qtek',
|
|
'RM' => 'RIM',
|
|
'RO' => 'Rover',
|
|
'SA' => 'Samsung',
|
|
'SD' => 'Sega',
|
|
'SE' => 'Sony Ericsson',
|
|
'SF' => 'Softbank',
|
|
'SG' => 'Sagem',
|
|
'SH' => 'Sharp',
|
|
'SI' => 'Siemens',
|
|
'SN' => 'Sendo',
|
|
'SO' => 'Sony',
|
|
'SP' => 'Spice',
|
|
'SU' => 'SuperSonic',
|
|
'SV' => 'Selevision',
|
|
'SY' => 'Sanyo',
|
|
'SM' => 'Symphony',
|
|
'SR' => 'Smart',
|
|
'TA' => 'Tesla',
|
|
'TC' => 'TCL',
|
|
'TE' => 'Telit',
|
|
'TH' => 'TiPhone',
|
|
'TI' => 'TIANYU',
|
|
'TL' => 'Telefunken',
|
|
'TM' => 'T-Mobile',
|
|
'TN' => 'Thomson',
|
|
'TO' => 'Toplux',
|
|
'TS' => 'Toshiba',
|
|
'TT' => 'TechnoTrend',
|
|
'TV' => 'TVC',
|
|
'TX' => 'TechniSat',
|
|
'TZ' => 'teXet',
|
|
'UT' => 'UTStarcom',
|
|
'VD' => 'Videocon',
|
|
'VE' => 'Vertu',
|
|
'VI' => 'Vitelcom',
|
|
'VK' => 'VK Mobile',
|
|
'VS' => 'ViewSonic',
|
|
'VT' => 'Vestel',
|
|
'VO' => 'Voxtel',
|
|
'VW' => 'Videoweb',
|
|
'WB' => 'Web TV',
|
|
'WE' => 'WellcoM',
|
|
'WO' => 'Wonu',
|
|
'WX' => 'Woxter',
|
|
'XI' => 'Xiaomi',
|
|
'XX' => 'Unknown',
|
|
'YU' => 'Yuandao',
|
|
'ZO' => 'Zonda',
|
|
'ZT' => 'ZTE',
|
|
);
|
|
public static $osShorts = array(
|
|
'AIX' => 'AIX',
|
|
'Android' => 'AND',
|
|
'AmigaOS' => 'AMG',
|
|
'Apple TV' => 'ATV',
|
|
'Arch Linux' => 'ARL',
|
|
'BackTrack' => 'BTR',
|
|
'Bada' => 'SBA',
|
|
'BeOS' => 'BEO',
|
|
'BlackBerry OS' => 'BLB',
|
|
'BlackBerry Tablet OS' => 'QNX',
|
|
'Bot' => 'BOT',
|
|
'Brew' => 'BMP',
|
|
'CentOS' => 'CES',
|
|
'Chrome OS' => 'COS',
|
|
'Debian' => 'DEB',
|
|
'DragonFly' => 'DFB',
|
|
'Fedora' => 'FED',
|
|
'Firefox OS' => 'FOS',
|
|
'FreeBSD' => 'BSD',
|
|
'Gentoo' => 'GNT',
|
|
'Google TV' => 'GTV',
|
|
'HP-UX' => 'HPX',
|
|
'Haiku OS' => 'HAI',
|
|
'IRIX' => 'IRI',
|
|
'Inferno' => 'INF',
|
|
'Knoppix' => 'KNO',
|
|
'Kubuntu' => 'KBT',
|
|
'Linux' => 'LIN',
|
|
'Lubuntu' => 'LBT',
|
|
'Mac' => 'MAC',
|
|
'Mandriva' => 'MDR',
|
|
'MeeGo' => 'SMG',
|
|
'Mint' => 'MIN',
|
|
'NetBSD' => 'NBS',
|
|
'Nintendo' => 'WII',
|
|
'Nintendo Mobile' => 'NDS',
|
|
'OS/2' => 'OS2',
|
|
'OSF1' => 'T64',
|
|
'OpenBSD' => 'OBS',
|
|
'PlayStation Portable' => 'PSP',
|
|
'PlayStation' => 'PS3',
|
|
'Presto' => 'PRS',
|
|
'Puppy' => 'PPY',
|
|
'Red Hat' => 'RHT',
|
|
'RISC OS' => 'ROS',
|
|
'Sabayon' => 'SAB',
|
|
'SUSE' => 'SSE',
|
|
'Sailfish OS' => 'SAF',
|
|
'Slackware' => 'SLW',
|
|
'Solaris' => 'SOS',
|
|
'Syllable' => 'SYL',
|
|
'Symbian' => 'SYM',
|
|
'Symbian OS' => 'SYS',
|
|
'Symbian OS Series 40' => 'S40',
|
|
'Symbian OS Series 60' => 'S60',
|
|
'Symbian^3' => 'SY3',
|
|
'Talkatone' => 'TKT',
|
|
'Tizen' => 'TIZ',
|
|
'Ubuntu' => 'UBT',
|
|
'WebTV' => 'WTV',
|
|
'WinWAP' => 'WWP',
|
|
'Windows' => 'WIN',
|
|
'Windows 2000' => 'W2K',
|
|
'Windows 3.1' => 'W31',
|
|
'Windows 7' => 'WI7',
|
|
'Windows 8' => 'WI8',
|
|
'Windows 95' => 'W95',
|
|
'Windows 98' => 'W98',
|
|
'Windows CE' => 'WCE',
|
|
'Windows ME' => 'WME',
|
|
'Windows Mobile' => 'WMO',
|
|
'Windows NT' => 'WNT',
|
|
'Windows Phone' => 'WPH',
|
|
'Windows RT' => 'WRT',
|
|
'Windows Server 2003' => 'WS3',
|
|
'Windows Vista' => 'WVI',
|
|
'Windows XP' => 'WXP',
|
|
'Xbox' => 'XBX',
|
|
'Xubuntu' => 'XBT',
|
|
'YunOs' => 'YNS',
|
|
'iOS' => 'IOS',
|
|
'palmOS' => 'POS',
|
|
'webOS' => 'WOS'
|
|
);
|
|
protected static $desktopOsArray = array('AmigaOS', 'IBM', 'Linux', 'Mac', 'Unix', 'Windows', 'BeOS');
|
|
public static $osFamilies = array(
|
|
'Android' => array('AND'),
|
|
'AmigaOS' => array('AMG'),
|
|
'Apple TV' => array('ATV'),
|
|
'BlackBerry' => array('BLB', 'QNX'),
|
|
'Bot' => array('BOT'),
|
|
'Brew' => array('BMP'),
|
|
'BeOS' => array('BEO', 'HAI'),
|
|
'Chrome OS' => array('COS'),
|
|
'Firefox OS' => array('FOS'),
|
|
'Gaming Console' => array('WII', 'PS3'),
|
|
'Google TV' => array('GTV'),
|
|
'IBM' => array('OS2'),
|
|
'iOS' => array('IOS'),
|
|
'RISC OS' => array('ROS'),
|
|
'Linux' => array('LIN', 'ARL', 'DEB', 'KNO', 'MIN', 'UBT', 'KBT', 'XBT', 'LBT', 'FED', 'RHT', 'MDR', 'GNT', 'SAB', 'SLW', 'SSE', 'PPY', 'CES', 'BTR', 'YNS', 'PRS', 'SAF'),
|
|
'Mac' => array('MAC'),
|
|
'Mobile Gaming Console' => array('PSP', 'NDS', 'XBX'),
|
|
'Other Mobile' => array('WOS', 'POS', 'SBA', 'TIZ', 'SMG'),
|
|
'Simulator' => array('TKT', 'WWP'),
|
|
'Symbian' => array('SYM', 'SYS', 'SY3', 'S60', 'S40'),
|
|
'Unix' => array('SOS', 'AIX', 'HPX', 'BSD', 'NBS', 'OBS', 'DFB', 'SYL', 'IRI', 'T64', 'INF'),
|
|
'WebTV' => array('WTV'),
|
|
'Windows' => array('WI7', 'WI8', 'WVI', 'WS3', 'WXP', 'W2K', 'WNT', 'WME', 'W98', 'W95', 'WRT', 'W31', 'WIN'),
|
|
'Windows Mobile' => array('WPH', 'WMO', 'WCE')
|
|
);
|
|
public static $browserFamilies = array(
|
|
'Android Browser' => array('AN'),
|
|
'BlackBerry Browser' => array('BB'),
|
|
'Chrome' => array('CH', 'CD', 'CM', 'CI', 'CF', 'CN', 'CR', 'CP', 'RM'),
|
|
'Firefox' => array('FF', 'FE', 'SX', 'FB', 'PX', 'MB'),
|
|
'Internet Explorer' => array('IE', 'IM'),
|
|
'Konqueror' => array('KO'),
|
|
'NetFront' => array('NF'),
|
|
'Nokia Browser' => array('NB', 'NO', 'NV'),
|
|
'Opera' => array('OP', 'OM', 'OI', 'ON'),
|
|
'Safari' => array('SF', 'MF'),
|
|
'Sailfish Browser' => array('SA')
|
|
);
|
|
public static $browsers = array(
|
|
'AA' => 'Avant Browser',
|
|
'AB' => 'ABrowse',
|
|
'AG' => 'ANTGalio',
|
|
'AM' => 'Amaya',
|
|
'AN' => 'Android Browser',
|
|
'AR' => 'Arora',
|
|
'AV' => 'Amiga Voyager',
|
|
'AW' => 'Amiga Aweb',
|
|
'BB' => 'BlackBerry Browser',
|
|
'BD' => 'Baidu Browser',
|
|
'BE' => 'Beonex',
|
|
'BJ' => 'Bunjalloo',
|
|
'BX' => 'BrowseX',
|
|
'CA' => 'Camino',
|
|
'CD' => 'Comodo Dragon',
|
|
'CX' => 'Charon',
|
|
'CF' => 'Chrome Frame',
|
|
'CH' => 'Chrome',
|
|
'CI' => 'Chrome Mobile iOS',
|
|
'CK' => 'Conkeror',
|
|
'CM' => 'Chrome Mobile',
|
|
'CN' => 'CoolNovo',
|
|
'CO' => 'CometBird',
|
|
'CP' => 'ChromePlus',
|
|
'CR' => 'Chromium',
|
|
'CS' => 'Cheshire',
|
|
'DF' => 'Dolphin',
|
|
'DI' => 'Dillo',
|
|
'EL' => 'Elinks',
|
|
'EP' => 'Epiphany',
|
|
'ES' => 'Espial TV Browser',
|
|
'FB' => 'Firebird',
|
|
'FD' => 'Fluid',
|
|
'FE' => 'Fennec',
|
|
'FF' => 'Firefox',
|
|
'FL' => 'Flock',
|
|
'FN' => 'Fireweb Navigator',
|
|
'GA' => 'Galeon',
|
|
'GE' => 'Google Earth',
|
|
'HJ' => 'HotJava',
|
|
'IA' => 'Iceape',
|
|
'IB' => 'IBrowse',
|
|
'IC' => 'iCab',
|
|
'ID' => 'IceDragon',
|
|
'IW' => 'Iceweasel',
|
|
'IE' => 'Internet Explorer',
|
|
'IM' => 'IE Mobile',
|
|
'IR' => 'Iron',
|
|
'JS' => 'Jasmine',
|
|
'KI' => 'Kindle Browser',
|
|
'KM' => 'K-meleon',
|
|
'KO' => 'Konqueror',
|
|
'KP' => 'Kapiko',
|
|
'KZ' => 'Kazehakase',
|
|
'LG' => 'Lightning',
|
|
'LI' => 'Links',
|
|
'LS' => 'Lunascape',
|
|
'LX' => 'Lynx',
|
|
'MB' => 'MicroB',
|
|
'MC' => 'NCSA Mosaic',
|
|
'ME' => 'Mercury',
|
|
'MF' => 'Mobile Safari',
|
|
'MI' => 'Midori',
|
|
'MS' => 'Mobile Silk',
|
|
'MX' => 'Maxthon',
|
|
'NB' => 'Nokia Browser',
|
|
'NO' => 'Nokia OSS Browser',
|
|
'NV' => 'Nokia Ovi Browser',
|
|
'NF' => 'NetFront',
|
|
'NL' => 'NetFront Life',
|
|
'NP' => 'NetPositive',
|
|
'NS' => 'Netscape',
|
|
'OB' => 'Obigo',
|
|
'OI' => 'Opera Mini',
|
|
'OM' => 'Opera Mobile',
|
|
'OP' => 'Opera',
|
|
'ON' => 'Opera Next',
|
|
'OR' => 'Oregano',
|
|
'OV' => 'Openwave Mobile Browser',
|
|
'OW' => 'OmniWeb',
|
|
'PL' => 'Palm Blazer',
|
|
'PM' => 'Pale Moon',
|
|
'PR' => 'Palm Pre',
|
|
'PU' => 'Puffin',
|
|
'PW' => 'Palm WebPro',
|
|
'PX' => 'Phoenix',
|
|
'PO' => 'Polaris',
|
|
'RK' => 'Rekonq',
|
|
'RM' => 'RockMelt',
|
|
'SA' => 'Sailfish Browser',
|
|
'SF' => 'Safari',
|
|
'SL' => 'Sleipnir',
|
|
'SM' => 'SeaMonkey',
|
|
'SN' => 'Snowshoe',
|
|
'SX' => 'Swiftfox',
|
|
'TB' => 'Thunderbird',
|
|
'TZ' => 'Tizen Browser',
|
|
'UC' => 'UC Browser',
|
|
'WE' => 'WebPositive',
|
|
'WO' => 'wOSBrowser',
|
|
'YA' => 'Yandex Browser',
|
|
'XI' => 'Xiino'
|
|
);
|
|
|
|
const UNKNOWN = "UNK";
|
|
protected static $regexesDir = '/regexes/';
|
|
protected static $osRegexesFile = 'oss.yml';
|
|
protected static $browserRegexesFile = 'browsers.yml';
|
|
protected static $mobileRegexesFile = 'mobiles.yml';
|
|
protected static $televisionRegexesFile = 'televisions.yml';
|
|
protected $userAgent;
|
|
protected $os = '';
|
|
protected $browser = '';
|
|
protected $device = '';
|
|
protected $brand = '';
|
|
protected $model = '';
|
|
protected $debug = false;
|
|
|
|
/**
|
|
* @var \Piwik\CacheFile
|
|
*/
|
|
protected $cache = null;
|
|
|
|
public function __construct($userAgent)
|
|
{
|
|
$this->userAgent = $userAgent;
|
|
}
|
|
|
|
protected function getOsRegexes()
|
|
{
|
|
static $regexOs;
|
|
if(empty($regexOs)) {
|
|
$regexOs = $this->getRegexList('os', self::$osRegexesFile);
|
|
}
|
|
return $regexOs;
|
|
}
|
|
|
|
protected function getBrowserRegexes()
|
|
{
|
|
static $regexBrowser;
|
|
if (empty($regexBrowser)) {
|
|
$regexBrowser = $this->getRegexList('browser', self::$browserRegexesFile);
|
|
}
|
|
return $regexBrowser;
|
|
}
|
|
|
|
protected function getMobileRegexes()
|
|
{
|
|
static $regexMobile;
|
|
if (empty($regexMobile)) {
|
|
$regexMobile = $this->getRegexList('mobile', self::$mobileRegexesFile);
|
|
}
|
|
return $regexMobile;
|
|
}
|
|
|
|
protected function getTelevisionRegexes()
|
|
{
|
|
static $regexTvs;
|
|
if (empty($regexTvs)) {
|
|
$regexTvs = $this->getRegexList('tv', self::$televisionRegexesFile);
|
|
}
|
|
return $regexTvs;
|
|
}
|
|
|
|
public function setCache($cache)
|
|
{
|
|
$this->cache = $cache;
|
|
}
|
|
|
|
protected function saveParsedYmlInCache($type, $data)
|
|
{
|
|
if (!empty($this->cache) && method_exists($this->cache, 'set')) {
|
|
$this->cache->set($type, serialize($data));
|
|
}
|
|
}
|
|
|
|
protected function getParsedYmlFromCache($type)
|
|
{
|
|
$data = null;
|
|
if (!empty($this->cache) && method_exists($this->cache, 'get')) {
|
|
$data = $this->cache->get($type);
|
|
if (!empty($data)) {
|
|
$data = unserialize($data);
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
|
|
public function parse()
|
|
{
|
|
$this->parseOs();
|
|
if ($this->isBot() || $this->isSimulator())
|
|
return;
|
|
|
|
$this->parseBrowser();
|
|
|
|
if($this->isHbbTv()) {
|
|
$this->parseTelevision();
|
|
} else {
|
|
$this->parseMobile();
|
|
}
|
|
|
|
if (empty($this->device) && $this->isHbbTv()) {
|
|
$this->device = array_search('tv', self::$deviceTypes);
|
|
} else if (empty($this->device) && $this->isDesktop()) {
|
|
$this->device = array_search('desktop', self::$deviceTypes);
|
|
}
|
|
|
|
/**
|
|
* Android up to 3.0 was designed for smartphones only. But as 3.0, which was tablet only, was published
|
|
* too late, there were a bunch of tablets running with 2.x
|
|
* With 4.0 the two trees were merged and it is for smartphones and tablets
|
|
*
|
|
* So were are expecting that all devices running Android < 2 are smartphones
|
|
* Devices running Android 3.X are tablets. Device type of Android 2.X and 4.X+ are unknown
|
|
*/
|
|
if (empty($this->device) && $this->getOs('short_name') == 'AND' && $this->getOs('version') != '') {
|
|
if (version_compare($this->getOs('version'), '2.0') == -1) {
|
|
$this->device = array_search('smartphone', self::$deviceTypes);
|
|
} else if (version_compare($this->getOs('version'), '3.0') >= 0 AND version_compare($this->getOs('version'), '4.0') == -1) {
|
|
$this->device = array_search('tablet', self::$deviceTypes);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* According to http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx
|
|
* Internet Explorer 10 introduces the "Touch" UA string token. If this token is present at the end of the
|
|
* UA string, the computer has touch capability, and is running Windows 8 (or later).
|
|
* This UA string will be transmitted on a touch-enabled system running Windows 8 (RT)
|
|
*
|
|
* As most touch enabled devices are tablets and only a smaller part are desktops/notebooks we assume that
|
|
* all Windows 8 touch devices are tablets.
|
|
*/
|
|
if (empty($this->device) && in_array($this->getOs('short_name'), array('WI8', 'WRT')) && $this->isTouchEnabled()) {
|
|
$this->device = array_search('tablet', self::$deviceTypes);
|
|
}
|
|
|
|
if ($this->debug) {
|
|
var_export($this->brand, $this->model, $this->device);
|
|
}
|
|
}
|
|
|
|
protected function parseOs()
|
|
{
|
|
foreach ($this->getOsRegexes() as $osRegex) {
|
|
$matches = $this->matchUserAgent($osRegex['regex']);
|
|
if ($matches)
|
|
break;
|
|
}
|
|
|
|
if (!$matches)
|
|
return;
|
|
|
|
$name = $this->buildOsName($osRegex['name'], $matches);
|
|
$short = 'UNK';
|
|
|
|
foreach (self::$osShorts AS $osName => $osShort) {
|
|
if (strtolower($name) == strtolower($osName)) {
|
|
$name = $osName;
|
|
$short = $osShort;
|
|
}
|
|
}
|
|
|
|
$this->os = array(
|
|
'name' => $name,
|
|
'short_name' => $short,
|
|
'version' => $this->buildOsVersion($osRegex['version'], $matches)
|
|
);
|
|
|
|
if (array_key_exists($this->os['name'], self::$osShorts)) {
|
|
$this->os['short_name'] = self::$osShorts[$this->os['name']];
|
|
}
|
|
}
|
|
|
|
protected function parseBrowser()
|
|
{
|
|
foreach ($this->getBrowserRegexes() as $browserRegex) {
|
|
$matches = $this->matchUserAgent($browserRegex['regex']);
|
|
if ($matches)
|
|
break;
|
|
}
|
|
|
|
if (!$matches)
|
|
return;
|
|
|
|
$name = $this->buildBrowserName($browserRegex['name'], $matches);
|
|
$short = 'XX';
|
|
|
|
foreach (self::$browsers AS $browserShort => $browserName) {
|
|
if (strtolower($name) == strtolower($browserName)) {
|
|
$name = $browserName;
|
|
$short = $browserShort;
|
|
}
|
|
}
|
|
|
|
$this->browser = array(
|
|
'name' => $name,
|
|
'short_name' => $short,
|
|
'version' => $this->buildBrowserVersion($browserRegex['version'], $matches)
|
|
);
|
|
}
|
|
|
|
protected function parseMobile()
|
|
{
|
|
$mobileRegexes = $this->getMobileRegexes();
|
|
$this->parseBrand($mobileRegexes);
|
|
$this->parseModel($mobileRegexes);
|
|
}
|
|
|
|
protected function parseTelevision()
|
|
{
|
|
$televisionRegexes = $this->getTelevisionRegexes();
|
|
$this->parseBrand($televisionRegexes);
|
|
$this->parseModel($televisionRegexes);
|
|
}
|
|
|
|
protected function parseBrand($deviceRegexes)
|
|
{
|
|
foreach ($deviceRegexes as $brand => $mobileRegex) {
|
|
$matches = $this->matchUserAgent($mobileRegex['regex']);
|
|
if ($matches)
|
|
break;
|
|
}
|
|
|
|
if (!$matches)
|
|
return;
|
|
|
|
$brandId = array_search($brand, self::$deviceBrands);
|
|
if($brandId === false) {
|
|
throw new Exception("The brand with name '$brand' should be listed in the deviceBrands array.");
|
|
}
|
|
$this->brand = $brandId;
|
|
$this->fullName = $brand;
|
|
|
|
if (isset($mobileRegex['device'])) {
|
|
$this->device = array_search($mobileRegex['device'], self::$deviceTypes);
|
|
}
|
|
|
|
if (isset($mobileRegex['model'])) {
|
|
$this->model = $this->buildModel($mobileRegex['model'], $matches);
|
|
}
|
|
}
|
|
|
|
protected function parseModel($deviceRegexes)
|
|
{
|
|
if (empty($this->brand) || !empty($this->model) || empty($deviceRegexes[$this->fullName]['models']))
|
|
return;
|
|
|
|
foreach ($deviceRegexes[$this->fullName]['models'] as $modelRegex) {
|
|
$matches = $this->matchUserAgent($modelRegex['regex']);
|
|
if ($matches)
|
|
break;
|
|
}
|
|
|
|
if (!$matches) {
|
|
return;
|
|
}
|
|
|
|
$this->model = trim($this->buildModel($modelRegex['model'], $matches));
|
|
|
|
if (isset($modelRegex['device'])) {
|
|
$this->device = array_search($modelRegex['device'], self::$deviceTypes);
|
|
}
|
|
}
|
|
|
|
protected function matchUserAgent($regex)
|
|
{
|
|
$regex = '/(?:^|[^A-Z_-])(?:' . str_replace('/', '\/', $regex) . ')/i';
|
|
|
|
if (preg_match($regex, $this->userAgent, $matches)) {
|
|
return $matches;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected function buildOsName($osName, $matches)
|
|
{
|
|
return $this->buildByMatch($osName, $matches);
|
|
}
|
|
|
|
protected function buildOsVersion($osVersion, $matches)
|
|
{
|
|
$osVersion = $this->buildByMatch($osVersion, $matches);
|
|
|
|
$osVersion = $this->buildByMatch($osVersion, $matches, '2');
|
|
|
|
$osVersion = str_replace('_', '.', $osVersion);
|
|
|
|
return $osVersion;
|
|
}
|
|
|
|
protected function buildBrowserName($browserName, $matches)
|
|
{
|
|
return $this->buildByMatch($browserName, $matches);
|
|
}
|
|
|
|
protected function buildBrowserVersion($browserVersion, $matches)
|
|
{
|
|
$browserVersion = $this->buildByMatch($browserVersion, $matches);
|
|
|
|
$browserVersion = $this->buildByMatch($browserVersion, $matches, '2');
|
|
|
|
$browserVersion = str_replace('_', '.', $browserVersion);
|
|
|
|
return $browserVersion;
|
|
}
|
|
|
|
protected function buildModel($model, $matches)
|
|
{
|
|
$model = $this->buildByMatch($model, $matches);
|
|
|
|
$model = $this->buildByMatch($model, $matches, '2');
|
|
|
|
$model = $this->buildModelExceptions($model);
|
|
|
|
$model = str_replace('_', ' ', $model);
|
|
|
|
return $model;
|
|
}
|
|
|
|
protected function buildModelExceptions($model)
|
|
{
|
|
if ($this->brand == 'O2') {
|
|
$model = preg_replace('/([a-z])([A-Z])/', '$1 $2', $model);
|
|
$model = ucwords(str_replace('_', ' ', $model));
|
|
}
|
|
|
|
return $model;
|
|
}
|
|
|
|
/**
|
|
* This method is used in this class for processing results of pregmatch
|
|
* results into string containing recognized information.
|
|
*
|
|
* General algorithm:
|
|
* Parsing UserAgent string consists of trying to match it against list of
|
|
* regular expressions for three different information:
|
|
* browser + version,
|
|
* OS + version,
|
|
* device manufacturer + model.
|
|
*
|
|
* After match has been found iteration stops, and results are processed
|
|
* by buildByMatch.
|
|
* As $item we get decoded name (name of browser, name of OS, name of manufacturer).
|
|
* In array $match we recieve preg_match results containing whole string matched at index 0
|
|
* and following matches in further indexes. Desired action now is to concatenate
|
|
* decoded name ($item) with matches found. First step is to append first found match,
|
|
* which is located in index=1 (that's why $nb is 1 by default).
|
|
* In other cases, where whe know that preg_match may return more than 1 result,
|
|
* we call buildByMatch with $nb = 2 or more, depending on what will be returned from
|
|
* regular expression.
|
|
*
|
|
* Example:
|
|
* We are parsing UserAgent of Firefox 20.0 browser.
|
|
* UserAgentParserEnhanced calls buildBrowserName() and buildBrowserVersion() in order
|
|
* to retrieve those information.
|
|
* In buildBrowserName() we only have one call of buildByMatch, where passed argument
|
|
* is regular expression testing given string for browser name. In this case, we are only
|
|
* interrested in first hit, so no $nb parameter will be set to 1. After finding match, and calling
|
|
* buildByMatch - we will receive just the name of browser.
|
|
*
|
|
* Also after decoding browser we will get list of regular expressions for this browser name
|
|
* testing UserAgent string for version number. Again we iterate over this list, and after finding first
|
|
* occurence - we break loop and proceed to build by match. Since browser regular expressions can
|
|
* contain two hits (major version and minor version) in function buildBrowserVersion() we have
|
|
* two calls to buildByMatch, one without 3rd parameter, and second with $nb set to 2.
|
|
* This way we can retrieve version number, and assign it to object property.
|
|
*
|
|
* In case of mobiles.yml this schema slightly varies, but general idea is the same.
|
|
*
|
|
* @param string $item
|
|
* @param array $matches
|
|
* @param int|string $nb
|
|
* @return string type
|
|
*/
|
|
protected function buildByMatch($item, $matches, $nb = '1')
|
|
{
|
|
if (strpos($item, '$' . $nb) === false)
|
|
return $item;
|
|
|
|
$replace = isset($matches[$nb]) ? $matches[$nb] : '';
|
|
return trim(str_replace('$' . $nb, $replace, $item));
|
|
}
|
|
|
|
public function isBot()
|
|
{
|
|
return $this->getOsFamily($this->getOs('short_name')) == 'Bot';
|
|
}
|
|
|
|
public function isSimulator()
|
|
{
|
|
return $this->getOsFamily($this->getOs('short_name')) == 'Simulator';
|
|
}
|
|
|
|
public function isHbbTv()
|
|
{
|
|
$regex = 'HbbTV/([1-9]{1}(\.[0-9]{1}){1,2})';
|
|
return $this->matchUserAgent($regex);
|
|
}
|
|
|
|
public function isTouchEnabled()
|
|
{
|
|
$regex = 'Touch';
|
|
return $this->matchUserAgent($regex);
|
|
}
|
|
|
|
public function isMobile()
|
|
{
|
|
return !$this->isDesktop();
|
|
}
|
|
|
|
public function isDesktop()
|
|
{
|
|
$osName = $this->getOs('name');
|
|
if (empty($osName) || empty(self::$osShorts[$osName])) {
|
|
return false;
|
|
}
|
|
|
|
$osShort = self::$osShorts[$osName];
|
|
foreach (self::$osFamilies as $family => $familyOs) {
|
|
if (in_array($osShort, $familyOs)) {
|
|
$decodedFamily = $family;
|
|
break;
|
|
}
|
|
}
|
|
return in_array($decodedFamily, self::$desktopOsArray);
|
|
}
|
|
|
|
public function getOs($attr = '')
|
|
{
|
|
if ($attr == '') {
|
|
return $this->os;
|
|
}
|
|
|
|
if (!isset($this->os[$attr])) {
|
|
return self::UNKNOWN;
|
|
}
|
|
|
|
return $this->os[$attr];
|
|
}
|
|
|
|
public function getBrowser($attr = '')
|
|
{
|
|
if ($attr == '') {
|
|
return $this->browser;
|
|
}
|
|
|
|
if (!isset($this->browser[$attr])) {
|
|
return self::UNKNOWN;
|
|
}
|
|
|
|
return $this->browser[$attr];
|
|
}
|
|
|
|
public function getDevice()
|
|
{
|
|
return $this->device;
|
|
}
|
|
|
|
public function getBrand()
|
|
{
|
|
return $this->brand;
|
|
}
|
|
|
|
public function getModel()
|
|
{
|
|
return $this->model;
|
|
}
|
|
|
|
public function getUserAgent()
|
|
{
|
|
return $this->userAgent;
|
|
}
|
|
|
|
/**
|
|
* @param $osLabel
|
|
* @return bool|string If false, "Unknown"
|
|
*/
|
|
public static function getOsFamily($osLabel)
|
|
{
|
|
foreach (self::$osFamilies as $family => $labels) {
|
|
if (in_array($osLabel, $labels)) {
|
|
return $family;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param $browserLabel
|
|
* @return bool|string If false, "Unknown"
|
|
*/
|
|
public static function getBrowserFamily($browserLabel)
|
|
{
|
|
foreach (self::$browserFamilies as $browserFamily => $browserLabels) {
|
|
if (in_array($browserLabel, $browserLabels)) {
|
|
return $browserFamily;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static function getOsNameFromId($os, $ver = false)
|
|
{
|
|
$osFullName = array_search($os, self::$osShorts);
|
|
if ($osFullName) {
|
|
if (in_array($os, self::$osFamilies['Windows'])) {
|
|
return $osFullName;
|
|
} else {
|
|
return trim($osFullName . " " . $ver);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static public function getInfoFromUserAgent($ua)
|
|
{
|
|
$userAgentParserEnhanced = new DeviceDetector($ua);
|
|
$userAgentParserEnhanced->parse();
|
|
|
|
$osFamily = $userAgentParserEnhanced->getOsFamily($userAgentParserEnhanced->getOs('short_name'));
|
|
$browserFamily = $userAgentParserEnhanced->getBrowserFamily($userAgentParserEnhanced->getBrowser('short_name'));
|
|
$device = $userAgentParserEnhanced->getDevice();
|
|
|
|
$deviceName = $device === '' ? '' : DeviceDetector::$deviceTypes[$device];
|
|
$processed = array(
|
|
'user_agent' => $userAgentParserEnhanced->getUserAgent(),
|
|
'os' => array(
|
|
'name' => $userAgentParserEnhanced->getOs('name'),
|
|
'short_name' => $userAgentParserEnhanced->getOs('short_name'),
|
|
'version' => $userAgentParserEnhanced->getOs('version'),
|
|
),
|
|
'browser' => array(
|
|
'name' => $userAgentParserEnhanced->getBrowser('name'),
|
|
'short_name' => $userAgentParserEnhanced->getBrowser('short_name'),
|
|
'version' => $userAgentParserEnhanced->getBrowser('version'),
|
|
),
|
|
'device' => array(
|
|
'type' => $deviceName,
|
|
'brand' => $userAgentParserEnhanced->getBrand(),
|
|
'model' => $userAgentParserEnhanced->getModel(),
|
|
),
|
|
'os_family' => $osFamily !== false ? $osFamily : 'Unknown',
|
|
'browser_family' => $browserFamily !== false ? $browserFamily : 'Unknown',
|
|
);
|
|
return $processed;
|
|
}
|
|
|
|
protected function getRegexList($type, $regexesFile)
|
|
{
|
|
$regexList = $this->getParsedYmlFromCache($type);
|
|
if (empty($regexList)) {
|
|
$regexList = Spyc::YAMLLoad(dirname(__FILE__) . self::$regexesDir . $regexesFile);
|
|
$this->saveParsedYmlInCache($type, $regexList);
|
|
}
|
|
return $regexList;
|
|
}
|
|
|
|
}
|