PageRenderTime 47ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/ua-parser/UAParser.php

https://github.com/bradstinson/ua-parser
PHP | 393 lines | 250 code | 56 blank | 87 comment | 95 complexity | aafa95bbb6d8aaec5598ac94a0a0500d MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. /*!
  3. * ua-parser-php v1.4.1
  4. *
  5. * Copyright (c) 2011-2012 Dave Olsen, http://dmolsen.com
  6. * Licensed under the MIT license
  7. *
  8. * ua-parser-php is the PHP library for the ua-parser project. Learn more about the ua-parser project at:
  9. *
  10. * https://github.com/tobie/ua-parser
  11. *
  12. * The user agents data from the ua-parser project is licensed under the Apache license.
  13. * spyc-0.5, for loading the YAML, is licensed under the MIT license.
  14. * The initial list of generic feature phones & smartphones came from Mobile Web OSP under the MIT license
  15. * The initial list of spiders was taken from Yiibu's profile project under the MIT license.
  16. *
  17. */
  18. // address 5.2 compatibility
  19. if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
  20. if (!function_exists('json_decode') || !function_exists('json_encode')) {
  21. require_once(__DIR__."/lib/json/jsonwrapper.php");
  22. }
  23. // load spyc as a YAML loader
  24. require_once(__DIR__."/lib/spyc-0.5/spyc.php");
  25. class UA {
  26. private static $ua;
  27. private static $accept;
  28. private static $regexes;
  29. private static $debug = false; // log requests
  30. public static $silent = true; // no output when running UA::get()
  31. public static $nobackup = false; // don't create a back-up when running UA::get()
  32. /**
  33. * Sets up some standard variables as well as starts the user agent parsing process
  34. *
  35. * @return {Object} the result of the user agent parsing
  36. */
  37. public static function parse($ua = NULL) {
  38. self::$ua = $ua ? $ua : strip_tags($_SERVER["HTTP_USER_AGENT"]);
  39. self::$accept = strip_tags($_SERVER["HTTP_ACCEPT"]);
  40. if (file_exists(__DIR__."/resources/regexes.yaml")) {
  41. self::$regexes = Spyc::YAMLLoad(__DIR__."/resources/regexes.yaml");
  42. } else {
  43. print "<h1>Error</h1>
  44. <p>Please download the regexes.yaml file before using UAParser.php.</p>
  45. <p>You can type the following at the command line to download the latest version:</p>
  46. <blockquote>
  47. <code>%: cd /path/to/UAParser</code><br />
  48. <code>%: php UAParser.php -get</code>
  49. </blockquote>";
  50. exit;
  51. }
  52. // run the regexes to match things up
  53. $uaRegexes = self::$regexes['user_agent_parsers'];
  54. foreach ($uaRegexes as $uaRegex) {
  55. if ($result = self::uaParser($uaRegex)) {
  56. $result->uaOriginal = self::$ua;
  57. break;
  58. }
  59. }
  60. // if no browser was found check to see if it can be matched at least against a device (e.g. spider, generic feature phone or generic smartphone)
  61. if (!$result) {
  62. if (($result = self::deviceParser()) && ($result->device != 'Spider')) {
  63. $result->isMobile = true;
  64. $result->isMobileDevice = true;
  65. $result->uaOriginal = self::$ua;
  66. } else if (isset($result) && isset($result->device) && ($result->device == "Spider")) {
  67. $result->isMobile = false;
  68. $result->isSpider = true;
  69. $result->uaOriginal = self::$ua;
  70. }
  71. }
  72. // still false?! see if it's a really dumb feature phone, if not just mark it as unknown
  73. if (!$result) {
  74. if ((strpos(self::$accept,'text/vnd.wap.wml') > 0) || (strpos(self::$accept,'application/vnd.wap.xhtml+xml') > 0) || isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])) {
  75. $result = new stdClass();
  76. $result->device = "Generic Feature Phone";
  77. $result->deviceFull = "Generic Feature Phone";
  78. $result->isMobile = true;
  79. $result->isMobileDevice = true;
  80. $result->uaOriginal = self::$ua;
  81. } else {
  82. $result = new stdClass();
  83. $result->device = "Unknown";
  84. $result->deviceFull = "Unknown";
  85. $result->isMobile = false;
  86. $result->isMobileDevice = false;
  87. $result->isComputer = true;
  88. $result->uaOriginal = self::$ua;
  89. }
  90. }
  91. // log the results when testing
  92. if (self::$debug) {
  93. self::log($result);
  94. }
  95. return $result;
  96. }
  97. /**
  98. * Attemps to see if the user agent matches the regex for this test. If so it populates an obj
  99. * with properties based on the user agent. Will also try to fetch OS & device properties
  100. *
  101. * @param {Array} the regex to be tested as well as any extra variables that need to be swapped
  102. *
  103. * @return {Object} the result of the user agent parsing
  104. */
  105. private static function uaParser($regex) {
  106. // tests the supplied regex against the user agent
  107. if (preg_match("/".str_replace("/","\/",$regex['regex'])."/",self::$ua,$matches)) {
  108. // Define safe parser defaults
  109. $defaults = array(
  110. 'isMobileDevice' => false,
  111. 'isMobile' => false,
  112. 'isSpider' => false,
  113. 'isTablet' => false,
  114. 'isComputer' => true,
  115. );
  116. // build the obj that will be returned starting with defaults
  117. $obj = (object) $defaults;
  118. // build the version numbers for the browser
  119. if (isset($matches[2]) || isset($regex['v1_replacement'])) {
  120. $obj->major = isset($regex['v1_replacement']) ? $regex['v1_replacement'] : $matches[2];
  121. } else {
  122. $obj->major = '';
  123. }
  124. if (isset($matches[3]) || isset($regex['v2_replacement'])) {
  125. $obj->minor = isset($regex['v2_replacement']) ? $regex['v2_replacement'] : $matches[3];
  126. }
  127. if (isset($matches[4])) {
  128. $obj->build = $matches[4];
  129. }
  130. if (isset($matches[5])) {
  131. $obj->revision = $matches[5];
  132. }
  133. // pull out the browser family. replace the version number if necessary
  134. $obj->browser = isset($regex['family_replacement']) ? str_replace("$1",$obj->major,$regex['family_replacement']) : $matches[1];
  135. // set-up a clean version number
  136. $obj->version = isset($obj->major) ? $obj->major : "";
  137. $obj->version = isset($obj->minor) ? $obj->version.'.'.$obj->minor : $obj->version;
  138. $obj->version = isset($obj->build) ? $obj->version.'.'.$obj->build : $obj->version;
  139. $obj->version = isset($obj->revision) ? $obj->version.'.'.$obj->revision : $obj->version;
  140. // prettify
  141. $obj->browserFull = $obj->browser;
  142. if ($obj->version != '') {
  143. $obj->browserFull .= " ".$obj->version;
  144. }
  145. // detect if this is a uiwebview call on iOS
  146. $obj->isUIWebview = (($obj->browser == 'Mobile Safari') && !strstr(self::$ua,'Safari')) ? true : false;
  147. // check to see if this is a mobile browser
  148. $mobileBrowsers = array("Firefox Mobile","Opera Mobile","Opera Mini","Mobile Safari","webOS","IE Mobile","Playstation Portable",
  149. "Nokia","Blackberry","Palm","Silk","Android","Maemo","Obigo","Netfront","AvantGo","Teleca","SEMC-Browser",
  150. "Bolt","Iris","UP.Browser","Symphony","Minimo","Bunjaloo","Jasmine","Dolfin","Polaris","BREW","Chrome Mobile",
  151. "UC Browser","Tizen Browser");
  152. foreach($mobileBrowsers as $mobileBrowser) {
  153. if (stristr($obj->browser, $mobileBrowser)) {
  154. $obj->isMobile = true;
  155. break;
  156. }
  157. }
  158. // figure out the OS for the browser, if possible
  159. if ($osObj = self::osParser()) {
  160. $obj = (object) array_merge((array) $obj, (array) $osObj);
  161. }
  162. // create an attribute combinining browser and os
  163. if (isset($obj->osFull) && $obj->osFull) {
  164. $obj->full = $obj->browserFull."/".$obj->osFull;
  165. }
  166. // figure out the device name for the browser, if possible
  167. if ($deviceObj = self::deviceParser()) {
  168. $obj = (object) array_merge((array) $obj, (array) $deviceObj);
  169. }
  170. // if this is a mobile browser make sure mobile device is set to true
  171. if ($obj->isMobile) {
  172. $obj->isMobileDevice = true; // this is going to catch android devices
  173. } else if ($obj->isMobileDevice) {
  174. $obj->isMobile = true; // this will catch some weird htc devices
  175. }
  176. // if OS is Android check to see if this is a tablet. won't work on UA strings less than Android 3.0
  177. // based on: http://googlewebmastercentral.blogspot.com/2011/03/mo-better-to-also-detect-mobile-user.html
  178. // opera doesn't follow this convention though...
  179. if ((isset($obj->os) && $obj->os == 'Android') && !strstr(self::$ua, 'Mobile') && !strstr(self::$ua, 'Opera')) {
  180. $obj->isTablet = true;
  181. $obj->isMobile = false;
  182. }
  183. // some select mobile OSs report a desktop browser. make sure we note they're mobile
  184. $mobileOSs = array('Windows Phone 6.5','Windows CE','Symbian OS');
  185. if (isset($obj->os) && in_array($obj->os,$mobileOSs)) {
  186. $obj->isMobile = true;
  187. $obj->isMobileDevice = true;
  188. }
  189. if (stristr(self::$ua,"tablet")) {
  190. $obj->isTablet = true;
  191. $obj->isMobileDevice = true;
  192. $obj->isMobile = false;
  193. }
  194. // record if this is a spider
  195. $obj->isSpider = (isset($obj->device) && $obj->device == "Spider") ? true : false;
  196. // record if this is a computer
  197. $obj->isComputer = (!$obj->isMobile && !$obj->isSpider && !$obj->isMobileDevice) ? true : false;
  198. return $obj;
  199. } else {
  200. return false;
  201. }
  202. }
  203. /**
  204. * If the user agent is matched in uaParser() it also tries to check the OS and get properties
  205. *
  206. * @return {Object} the result of the os parsing
  207. */
  208. private static function osParser() {
  209. // build the obj that will be returned
  210. $osObj = new stdClass();
  211. // run the regexes to match things up
  212. $osRegexes = self::$regexes['os_parsers'];
  213. foreach ($osRegexes as $osRegex) {
  214. if (preg_match("/".str_replace("/","\/",$osRegex['regex'])."/",self::$ua,$matches)) {
  215. // Make sure matches 2 and 3 are at least set to null for setting
  216. // Major and Minor defaults
  217. if (!isset($matches[1])) { $matches[1] = null; }
  218. if (!isset($matches[2])) { $matches[2] = null; }
  219. if (!isset($matches[3])) { $matches[3] = null; }
  220. // basic properties
  221. $osObj->osMajor = isset($osRegex['os_v1_replacement']) ? $osRegex['os_v1_replacement'] : $matches[2];
  222. $osObj->osMinor = isset($osRegex['os_v2_replacement']) ? $osRegex['os_v2_replacement'] : $matches[3];
  223. if (isset($matches[4])) {
  224. $osObj->osBuild = $matches[4];
  225. }
  226. if (isset($matches[5])) {
  227. $osObj->osRevision = $matches[5];
  228. }
  229. $osObj->os = isset($osRegex['os_replacement']) ? str_replace("$1",$osObj->osMajor,$osRegex['os_replacement']) : $matches[1];
  230. // os version
  231. $osObj->osVersion = isset($osObj->osMajor) ? $osObj->osMajor : "";
  232. $osObj->osVersion = isset($osObj->osMinor) ? $osObj->osVersion.'.'.$osObj->osMinor : $osObj->osVersion;
  233. $osObj->osVersion = isset($osObj->osBuild) ? $osObj->osVersion.'.'.$osObj->osBuild : $osObj->osVersion;
  234. $osObj->osVersion = isset($osObj->osRevision) ? $osObj->osVersion.'.'.$osObj->osRevision : $osObj->osVersion;
  235. // prettify
  236. $osObj->osFull = $osObj->os." ".$osObj->osVersion;
  237. return $osObj;
  238. }
  239. }
  240. return false;
  241. }
  242. /**
  243. * If the user agent is matched in uaParser() it also tries to check the device and get its properties
  244. *
  245. * @return {Object} the result of the device parsing
  246. */
  247. private static function deviceParser() {
  248. // build the obj that will be returned
  249. $deviceObj = new stdClass();
  250. // run the regexes to match things up
  251. $deviceRegexes = self::$regexes['device_parsers'];
  252. foreach ($deviceRegexes as $deviceRegex) {
  253. if (preg_match("/".str_replace("/","\/",$deviceRegex['regex'])."/i",self::$ua,$matches)) {
  254. // Make sure device matches are null
  255. // Device Name, Major and Minor defaults
  256. if (!isset($matches[1])) { $matches[1] = null; }
  257. if (!isset($matches[2])) { $matches[2] = null; }
  258. if (!isset($matches[3])) { $matches[3] = null; }
  259. // basic properties
  260. $deviceObj->deviceMajor = isset($deviceRegex['device_v1_replacement']) ? $deviceRegex['device_v1_replacement'] : $matches[2];
  261. $deviceObj->deviceMinor = isset($deviceRegex['device_v2_replacement']) ? $deviceRegex['device_v2_replacement'] : $matches[3];
  262. $deviceObj->device = isset($deviceRegex['device_replacement']) ? str_replace("$1",$matches[1],$deviceRegex['device_replacement']) : str_replace("_"," ",$matches[1]);
  263. // device version?
  264. $deviceObj->deviceVersion = isset($deviceObj->deviceMajor) ? $deviceObj->deviceMajor : "";
  265. $deviceObj->deviceVersion = isset($deviceObj->deviceMinor) ? $deviceObj->deviceVersion.'.'.$deviceObj->deviceMinor : $deviceObj->deviceVersion;
  266. // prettify
  267. $deviceObj->deviceFull = $deviceObj->device." ".$deviceObj->deviceVersion;
  268. // check to see if this is a mobile device
  269. // this isn't really needed because if it matches a mobile browser it'll automatically mark it as a mobile device
  270. $deviceObj->isMobileDevice = false;
  271. $mobileDevices = array("iPhone","iPod","iPad","HTC","Kindle","Lumia","Amoi","Asus","Bird","Dell","DoCoMo","Huawei","i-mate","Kyocera",
  272. "Lenovo","LG","Kin","Motorola","Philips","Samsung","Softbank","Palm","HP ","Generic Feature Phone","Generic Smartphone");
  273. foreach($mobileDevices as $mobileDevice) {
  274. if (stristr($deviceObj->device, $mobileDevice)) {
  275. $deviceObj->isMobileDevice = true;
  276. break;
  277. }
  278. }
  279. // check to see if this is a tablet (not perfect)
  280. $deviceObj->isTablet = false;
  281. $tablets = array("Kindle","iPad","Playbook","TouchPad","Dell Streak","Galaxy Tab","Xoom");
  282. foreach($tablets as $tablet) {
  283. if (stristr($deviceObj->device, $tablet)) {
  284. $deviceObj->isTablet = true;
  285. break;
  286. }
  287. }
  288. return $deviceObj;
  289. }
  290. }
  291. return false;
  292. }
  293. /**
  294. * Logs the user agent info
  295. */
  296. private static function log($data) {
  297. if (!$data) {
  298. $data = new stdClass();
  299. $data->ua = self::$ua;
  300. }
  301. $jsonData = json_encode($data);
  302. $fp = fopen(__DIR__."/log/user_agents.log", "a");
  303. fwrite($fp, $jsonData."\r\n");
  304. fclose($fp);
  305. }
  306. /**
  307. * Gets the latest user agent. Back-ups the old version first. it will fail silently if something is wrong...
  308. */
  309. public static function get() {
  310. if ($data = @file_get_contents("https://raw.github.com/tobie/ua-parser/master/regexes.yaml")) {
  311. if (file_exists(__DIR__."/resources/regexes.yaml")) {
  312. if (!self::$nobackup) {
  313. if (!self::$silent) { print("backing up old YAML file...\n"); }
  314. if (!copy(__DIR__."/resources/regexes.yaml", __DIR__."/resources/regexes.".date("Ymdhis").".yaml")) {
  315. if (!self::$silent) { print("back-up failed...\n"); }
  316. exit;
  317. }
  318. }
  319. }
  320. $fp = fopen(__DIR__."/resources/regexes.yaml", "w");
  321. fwrite($fp, $data);
  322. fclose($fp);
  323. if (!self::$silent) { print("success...\n"); }
  324. } else {
  325. if (!self::$silent) { print("failed to get the file...\n"); }
  326. }
  327. }
  328. }
  329. if (defined('STDIN') && isset($argv) && isset($argv[1]) && ($argv[1] == '-get')) {
  330. UA::$silent = ((isset($argv[2]) && ($argv[2] == '-silent')) || (isset($argv[3]) && ($argv[3] == '-silent'))) ? true : UA::$silent;
  331. UA::$nobackup = ((isset($argv[2]) && ($argv[2] == '-nobackup')) || (isset($argv[3]) && ($argv[3] == '-nobackup'))) ? true : UA::$nobackup;
  332. if (!UA::$silent) { print("getting the YAML file...\n"); }
  333. UA::get();
  334. } else if (defined('STDIN')) {
  335. print("You must use the -get flag to use UAParser.php from the command line.\n");
  336. }
  337. ?>