PageRenderTime 50ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/classes/useragent.php

https://bitbucket.org/synergylearning/campusconnect
PHP | 910 lines | 497 code | 60 blank | 353 comment | 145 complexity | 34ea3e592f2f5898e26457c66fc01be4 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, LGPL-2.1, Apache-2.0, BSD-3-Clause, AGPL-3.0
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Environment class to aid with the detection and establishment of the working environment.
  18. *
  19. * @package core
  20. * @copyright 2013 Sam Hemelryk
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. /**
  24. * The user agent class.
  25. *
  26. * It's important to note that we do not like browser sniffing and its use in core code is highly discouraged.
  27. * No new uses of this API will be integrated unless there is absolutely no alternative.
  28. *
  29. * This API supports the few browser checks we do have in core, all of which one day will hopefully be removed.
  30. * The API will remain to support any third party use out there, however at some point like all code it will be deprecated.
  31. *
  32. * Use sparingly and only with good cause!
  33. *
  34. * @package core
  35. * @copyright 2013 Sam Hemelryk
  36. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37. */
  38. class core_useragent {
  39. /**
  40. * The default for devices, think of as a computer.
  41. */
  42. const DEVICETYPE_DEFAULT = 'default';
  43. /**
  44. * Legacy devices, or at least legacy browsers. These are older devices/browsers
  45. * that don't support standards.
  46. */
  47. const DEVICETYPE_LEGACY = 'legacy';
  48. /**
  49. * Mobile devices like your cell phone or hand held gaming device.
  50. */
  51. const DEVICETYPE_MOBILE = 'mobile';
  52. /**
  53. * Tables, larger than hand held, but still easily portable and smaller than a laptop.
  54. */
  55. const DEVICETYPE_TABLET = 'tablet';
  56. /**
  57. * An instance of this class.
  58. * @var core_useragent
  59. */
  60. protected static $instance = null;
  61. /**
  62. * The device types we track.
  63. * @var array
  64. */
  65. public static $devicetypes = array(
  66. self::DEVICETYPE_DEFAULT,
  67. self::DEVICETYPE_LEGACY,
  68. self::DEVICETYPE_MOBILE,
  69. self::DEVICETYPE_TABLET
  70. );
  71. /**
  72. * The current requests user agent string if there was one.
  73. * @var string|bool|null Null until initialised, false if none available, or string when available.
  74. */
  75. protected $useragent = null;
  76. /**
  77. * The users device type, one of self::DEVICETYPE_*.
  78. * @var string null until initialised
  79. */
  80. protected $devicetype = null;
  81. /**
  82. * Custom device types entered into the admin interface.
  83. * @var array
  84. */
  85. protected $devicetypecustoms = array();
  86. /**
  87. * True if the user agent supports the display of svg images. False if not.
  88. * @var bool|null Null until initialised, then true or false.
  89. */
  90. protected $supportssvg = null;
  91. /**
  92. * Get an instance of the user agent object.
  93. *
  94. * @param bool $reload If set to true the user agent will be reset and all ascertations remade.
  95. * @param string $forceuseragent The string to force as the user agent, don't use unless absolutely unavoidable.
  96. * @return core_useragent
  97. */
  98. public static function instance($reload = false, $forceuseragent = null) {
  99. if (!self::$instance || $reload) {
  100. self::$instance = new core_useragent($forceuseragent);
  101. }
  102. return self::$instance;
  103. }
  104. /**
  105. * Constructs a new user agent object. Publically you must use the instance method above.
  106. *
  107. * @param string|null $forceuseragent Optional a user agent to force.
  108. */
  109. protected function __construct($forceuseragent = null) {
  110. global $CFG;
  111. if (!empty($CFG->devicedetectregex)) {
  112. $this->devicetypecustoms = json_decode($CFG->devicedetectregex, true);
  113. }
  114. if ($this->devicetypecustoms === null) {
  115. // This shouldn't happen unless you're hardcoding the config value.
  116. debugging('Config devicedetectregex is not valid JSON object');
  117. $this->devicetypecustoms = array();
  118. }
  119. if ($forceuseragent !== null) {
  120. $this->useragent = $forceuseragent;
  121. } else if (!empty($_SERVER['HTTP_USER_AGENT'])) {
  122. $this->useragent = $_SERVER['HTTP_USER_AGENT'];
  123. } else {
  124. $this->useragent = false;
  125. $this->devicetype = self::DEVICETYPE_DEFAULT;
  126. }
  127. }
  128. /**
  129. * Returns the user agent string.
  130. * @return bool|string The user agent string or false if one isn't available.
  131. */
  132. public static function get_user_agent_string() {
  133. $instance = self::instance();
  134. return $instance->useragent;
  135. }
  136. /**
  137. * Returns the device type we believe is being used.
  138. * @return string
  139. */
  140. public static function get_device_type() {
  141. $instance = self::instance();
  142. if ($instance->devicetype === null) {
  143. return $instance->guess_device_type();
  144. }
  145. return $instance->devicetype;
  146. }
  147. /**
  148. * Guesses the device type the user agent is running on.
  149. *
  150. * @return string
  151. */
  152. protected function guess_device_type() {
  153. global $CFG;
  154. if (empty($CFG->enabledevicedetection)) {
  155. $this->devicetype = self::DEVICETYPE_DEFAULT;
  156. return $this->devicetype;
  157. }
  158. foreach ($this->devicetypecustoms as $value => $regex) {
  159. if (preg_match($regex, $this->useragent)) {
  160. $this->devicetype = $value;
  161. return $this->devicetype;
  162. }
  163. }
  164. if ($this->is_useragent_mobile()) {
  165. $this->devicetype = 'mobile';
  166. } else if ($this->is_useragent_tablet()) {
  167. $this->devicetype = 'tablet';
  168. } else if (substr($this->useragent, 0, 34) === 'Mozilla/4.0 (compatible; MSIE 6.0;') {
  169. // Safe way to check for IE6 and not get false positives for some IE 7/8 users.
  170. $this->devicetype = 'legacy';
  171. } else {
  172. $this->devicetype = self::DEVICETYPE_DEFAULT;
  173. }
  174. return $this->devicetype;
  175. }
  176. /**
  177. * Returns true if the user appears to be on a mobile device.
  178. * @return bool
  179. */
  180. protected function is_useragent_mobile() {
  181. // Mobile detection PHP direct copy from open source detectmobilebrowser.com.
  182. $phonesregex = '/android .+ mobile|avantgo|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i';
  183. $modelsregex = '/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i';
  184. return (preg_match($phonesregex, $this->useragent) || preg_match($modelsregex, substr($this->useragent, 0, 4)));
  185. }
  186. /**
  187. * Returns true if the user appears to be on a tablet.
  188. * @return int
  189. */
  190. protected function is_useragent_tablet() {
  191. $tabletregex = '/Tablet browser|android|iPad|iProd|GT-P1000|GT-I9000|SHW-M180S|SGH-T849|SCH-I800|Build\/ERE27|sholest/i';
  192. return (preg_match($tabletregex, $this->useragent));
  193. }
  194. /**
  195. * Gets a list of known device types.
  196. *
  197. * @param bool $includecustomtypes If set to true we'll include types that have been added by the admin.
  198. * @return array
  199. */
  200. public static function get_device_type_list($includecustomtypes = true) {
  201. $types = self::$devicetypes;
  202. if ($includecustomtypes) {
  203. $instance = self::instance();
  204. $types = array_merge($types, array_keys($instance->devicetypecustoms));
  205. }
  206. return $types;
  207. }
  208. /**
  209. * Returns the theme to use for the given device type.
  210. *
  211. * This used to be get_selected_theme_for_device_type.
  212. * @param null|string $devicetype The device type to find out for. Defaults to the device the user is using,
  213. * @return bool
  214. */
  215. public static function get_device_type_theme($devicetype = null) {
  216. global $CFG;
  217. if ($devicetype === null) {
  218. $devicetype = self::get_device_type();
  219. }
  220. $themevarname = self::get_device_type_cfg_var_name($devicetype);
  221. if (empty($CFG->$themevarname)) {
  222. return false;
  223. }
  224. return $CFG->$themevarname;
  225. }
  226. /**
  227. * Returns the CFG var used to find the theme to use for the given device.
  228. *
  229. * Used to be get_device_cfg_var_name.
  230. *
  231. * @param null|string $devicetype The device type to find out for. Defaults to the device the user is using,
  232. * @return string
  233. */
  234. public static function get_device_type_cfg_var_name($devicetype = null) {
  235. if ($devicetype == self::DEVICETYPE_DEFAULT || empty($devicetype)) {
  236. return 'theme';
  237. }
  238. return 'theme' . $devicetype;
  239. }
  240. /**
  241. * Gets the device type the user is currently using.
  242. * @return string
  243. */
  244. public static function get_user_device_type() {
  245. $device = self::get_device_type();
  246. $switched = get_user_preferences('switchdevice'.$device, false);
  247. if ($switched != false) {
  248. return $switched;
  249. }
  250. return $device;
  251. }
  252. /**
  253. * Switches the device type we think the user is using to what ever was given.
  254. * @param string $newdevice
  255. * @return bool
  256. * @throws coding_exception
  257. */
  258. public static function set_user_device_type($newdevice) {
  259. $devicetype = self::get_device_type();
  260. if ($newdevice == $devicetype) {
  261. unset_user_preference('switchdevice'.$devicetype);
  262. return true;
  263. } else {
  264. $devicetypes = self::get_device_type_list();
  265. if (in_array($newdevice, $devicetypes)) {
  266. set_user_preference('switchdevice'.$devicetype, $newdevice);
  267. return true;
  268. }
  269. }
  270. throw new coding_exception('Invalid device type provided to set_user_device_type');
  271. }
  272. /**
  273. * Returns true if the user agent matches the given brand and the version is equal to or greater than that specified.
  274. *
  275. * @param string $brand The branch to check for.
  276. * @param scalar $version The version if we need to find out if it is equal to or greater than that specified.
  277. * @return bool
  278. */
  279. public static function check_browser_version($brand, $version = null) {
  280. switch ($brand) {
  281. case 'MSIE':
  282. // Internet Explorer.
  283. return self::check_ie_version($version);
  284. case 'Firefox':
  285. // Mozilla Firefox browsers.
  286. return self::check_firefox_version($version);
  287. case 'Chrome':
  288. return self::check_chrome_version($version);
  289. case 'Opera':
  290. // Opera.
  291. return self::check_opera_version($version);
  292. case 'Safari':
  293. // Desktop version of Apple Safari browser - no mobile or touch devices.
  294. return self::check_safari_version($version);
  295. case 'Safari iOS':
  296. // Safari on iPhone, iPad and iPod touch.
  297. return self::check_safari_ios_version($version);
  298. case 'WebKit':
  299. // WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles).
  300. return self::check_webkit_version($version);
  301. case 'Gecko':
  302. // Gecko based browsers.
  303. return self::check_gecko_version($version);
  304. case 'WebKit Android':
  305. // WebKit browser on Android.
  306. return self::check_webkit_android_version($version);
  307. case 'Camino':
  308. // OSX browser using Gecke engine.
  309. return self::check_camino_version($version);
  310. }
  311. // Who knows?! doesn't pass anyway.
  312. return false;
  313. }
  314. /**
  315. * Checks the user agent is camino based and that the version is equal to or greater than that specified.
  316. *
  317. * Camino browser is at the end of its life, its no longer being developed or supported, just don't worry about it.
  318. *
  319. * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
  320. * @return bool
  321. */
  322. protected static function check_camino_version($version = null) {
  323. // OSX browser using Gecko engine.
  324. $useragent = self::get_user_agent_string();
  325. if ($useragent === false) {
  326. return false;
  327. }
  328. if (strpos($useragent, 'Camino') === false) {
  329. return false;
  330. }
  331. if (empty($version)) {
  332. return true; // No version specified.
  333. }
  334. if (preg_match("/Camino\/([0-9\.]+)/i", $useragent, $match)) {
  335. if (version_compare($match[1], $version) >= 0) {
  336. return true;
  337. }
  338. }
  339. return false;
  340. }
  341. /**
  342. * Checks the user agent is Firefox (of any version).
  343. *
  344. * @return bool true if firefox
  345. */
  346. public static function is_firefox() {
  347. return self::check_firefox_version();
  348. }
  349. /**
  350. * Checks the user agent is Firefox based and that the version is equal to or greater than that specified.
  351. *
  352. * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
  353. * @return bool
  354. */
  355. public static function check_firefox_version($version = null) {
  356. // Mozilla Firefox browsers.
  357. $useragent = self::get_user_agent_string();
  358. if ($useragent === false) {
  359. return false;
  360. }
  361. if (strpos($useragent, 'Firefox') === false && strpos($useragent, 'Iceweasel') === false) {
  362. return false;
  363. }
  364. if (empty($version)) {
  365. return true; // No version specified..
  366. }
  367. if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $useragent, $match)) {
  368. if (version_compare($match[2], $version) >= 0) {
  369. return true;
  370. }
  371. }
  372. return false;
  373. }
  374. /**
  375. * Checks the user agent is Gecko based (of any version).
  376. *
  377. * @return bool true if Gecko based.
  378. */
  379. public static function is_gecko() {
  380. return self::check_gecko_version();
  381. }
  382. /**
  383. * Checks the user agent is Gecko based and that the version is equal to or greater than that specified.
  384. *
  385. * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
  386. * @return bool
  387. */
  388. public static function check_gecko_version($version = null) {
  389. // Gecko based browsers.
  390. // Do not look for dates any more, we expect real Firefox version here.
  391. $useragent = self::get_user_agent_string();
  392. if ($useragent === false) {
  393. return false;
  394. }
  395. if (empty($version)) {
  396. $version = 1;
  397. } else if ($version > 20000000) {
  398. // This is just a guess, it is not supposed to be 100% accurate!
  399. if (preg_match('/^201/', $version)) {
  400. $version = 3.6;
  401. } else if (preg_match('/^200[7-9]/', $version)) {
  402. $version = 3;
  403. } else if (preg_match('/^2006/', $version)) {
  404. $version = 2;
  405. } else {
  406. $version = 1.5;
  407. }
  408. }
  409. if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $useragent, $match)) {
  410. // Use real Firefox version if specified in user agent string.
  411. if (version_compare($match[2], $version) >= 0) {
  412. return true;
  413. }
  414. } else if (preg_match("/Gecko\/([0-9\.]+)/i", $useragent, $match)) {
  415. // Gecko might contain date or Firefox revision, let's just guess the Firefox version from the date.
  416. $browserver = $match[1];
  417. if ($browserver > 20000000) {
  418. // This is just a guess, it is not supposed to be 100% accurate!
  419. if (preg_match('/^201/', $browserver)) {
  420. $browserver = 3.6;
  421. } else if (preg_match('/^200[7-9]/', $browserver)) {
  422. $browserver = 3;
  423. } else if (preg_match('/^2006/', $version)) {
  424. $browserver = 2;
  425. } else {
  426. $browserver = 1.5;
  427. }
  428. }
  429. if (version_compare($browserver, $version) >= 0) {
  430. return true;
  431. }
  432. }
  433. return false;
  434. }
  435. /**
  436. * Checks the user agent is IE (of any version).
  437. *
  438. * @return bool true if internet exporeer
  439. */
  440. public static function is_ie() {
  441. return self::check_ie_version();
  442. }
  443. /**
  444. * Checks the user agent is IE and returns its main properties:
  445. * - browser version;
  446. * - whether running in compatibility view.
  447. *
  448. * @return bool|array False if not IE, otherwise an associative array of properties.
  449. */
  450. public static function check_ie_properties() {
  451. // Internet Explorer.
  452. $useragent = self::get_user_agent_string();
  453. if ($useragent === false) {
  454. return false;
  455. }
  456. if (strpos($useragent, 'Opera') !== false) {
  457. // Reject Opera.
  458. return false;
  459. }
  460. // See: http://www.useragentstring.com/pages/Internet%20Explorer/.
  461. if (preg_match("/MSIE ([0-9\.]+)/", $useragent, $match)) {
  462. $browser = $match[1];
  463. // See: http://msdn.microsoft.com/en-us/library/ie/bg182625%28v=vs.85%29.aspx for IE11+ useragent details.
  464. } else if (preg_match("/Trident\/[0-9\.]+/", $useragent) && preg_match("/rv:([0-9\.]+)/", $useragent, $match)) {
  465. $browser = $match[1];
  466. } else {
  467. return false;
  468. }
  469. $compatview = false;
  470. // IE8 and later versions may pretend to be IE7 for intranet sites, use Trident version instead,
  471. // the Trident should always describe the capabilities of IE in any emulation mode.
  472. if ($browser === '7.0' and preg_match("/Trident\/([0-9\.]+)/", $useragent, $match)) {
  473. $compatview = true;
  474. $browser = $match[1] + 4; // NOTE: Hopefully this will work also for future IE versions.
  475. }
  476. $browser = round($browser, 1);
  477. return array(
  478. 'version' => $browser,
  479. 'compatview' => $compatview
  480. );
  481. }
  482. /**
  483. * Checks the user agent is IE and that the version is equal to or greater than that specified.
  484. *
  485. * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
  486. * @return bool
  487. */
  488. public static function check_ie_version($version = null) {
  489. // Internet Explorer.
  490. $properties = self::check_ie_properties();
  491. if (!is_array($properties)) {
  492. return false;
  493. }
  494. // In case of IE we have to deal with BC of the version parameter.
  495. if (is_null($version)) {
  496. $version = 5.5; // Anything older is not considered a browser at all!
  497. }
  498. // IE uses simple versions, let's cast it to float to simplify the logic here.
  499. $version = round($version, 1);
  500. return ($properties['version'] >= $version);
  501. }
  502. /**
  503. * Checks the user agent is IE and that IE is running under Compatibility View setting.
  504. *
  505. * @return bool true if internet explorer runs in Compatibility View mode.
  506. */
  507. public static function check_ie_compatibility_view() {
  508. // IE User Agent string when in Compatibility View:
  509. // - IE 8: "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/4.0; ...)".
  510. // - IE 9: "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/5.0; ...)".
  511. // - IE 10: "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/6.0; ...)".
  512. // - IE 11: "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0; ...)".
  513. // Refs:
  514. // - http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx.
  515. // - http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx.
  516. // - http://blogs.msdn.com/b/ie/archive/2011/04/15/the-ie10-user-agent-string.aspx.
  517. // - http://msdn.microsoft.com/en-us/library/ie/hh869301%28v=vs.85%29.aspx.
  518. $properties = self::check_ie_properties();
  519. if (!is_array($properties)) {
  520. return false;
  521. }
  522. return $properties['compatview'];
  523. }
  524. /**
  525. * Checks the user agent is Opera (of any version).
  526. *
  527. * @return bool true if opera
  528. */
  529. public static function is_opera() {
  530. return self::check_opera_version();
  531. }
  532. /**
  533. * Checks the user agent is Opera and that the version is equal to or greater than that specified.
  534. *
  535. * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
  536. * @return bool
  537. */
  538. public static function check_opera_version($version = null) {
  539. // Opera.
  540. $useragent = self::get_user_agent_string();
  541. if ($useragent === false) {
  542. return false;
  543. }
  544. if (strpos($useragent, 'Opera') === false) {
  545. return false;
  546. }
  547. if (empty($version)) {
  548. return true; // No version specified.
  549. }
  550. // Recent Opera useragents have Version/ with the actual version, e.g.:
  551. // Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.289 Version/12.01
  552. // That's Opera 12.01, not 9.8.
  553. if (preg_match("/Version\/([0-9\.]+)/i", $useragent, $match)) {
  554. if (version_compare($match[1], $version) >= 0) {
  555. return true;
  556. }
  557. } else if (preg_match("/Opera\/([0-9\.]+)/i", $useragent, $match)) {
  558. if (version_compare($match[1], $version) >= 0) {
  559. return true;
  560. }
  561. }
  562. return false;
  563. }
  564. /**
  565. * Checks the user agent is webkit based
  566. *
  567. * @return bool true if webkit
  568. */
  569. public static function is_webkit() {
  570. return self::check_webkit_version();
  571. }
  572. /**
  573. * Checks the user agent is Webkit based and that the version is equal to or greater than that specified.
  574. *
  575. * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
  576. * @return bool
  577. */
  578. public static function check_webkit_version($version = null) {
  579. // WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles).
  580. $useragent = self::get_user_agent_string();
  581. if ($useragent === false) {
  582. return false;
  583. }
  584. if (strpos($useragent, 'AppleWebKit') === false) {
  585. return false;
  586. }
  587. if (empty($version)) {
  588. return true; // No version specified.
  589. }
  590. if (preg_match("/AppleWebKit\/([0-9.]+)/i", $useragent, $match)) {
  591. if (version_compare($match[1], $version) >= 0) {
  592. return true;
  593. }
  594. }
  595. return false;
  596. }
  597. /**
  598. * Checks the user agent is Safari
  599. *
  600. * @return bool true if safari
  601. */
  602. public static function is_safari() {
  603. return self::check_safari_version();
  604. }
  605. /**
  606. * Checks the user agent is Safari based and that the version is equal to or greater than that specified.
  607. *
  608. * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
  609. * @return bool
  610. */
  611. public static function check_safari_version($version = null) {
  612. // Desktop version of Apple Safari browser - no mobile or touch devices.
  613. $useragent = self::get_user_agent_string();
  614. if ($useragent === false) {
  615. return false;
  616. }
  617. if (strpos($useragent, 'AppleWebKit') === false) {
  618. return false;
  619. }
  620. // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices.
  621. if (strpos($useragent, 'OmniWeb')) {
  622. // Reject OmniWeb.
  623. return false;
  624. }
  625. if (strpos($useragent, 'Shiira')) {
  626. // Reject Shiira.
  627. return false;
  628. }
  629. if (strpos($useragent, 'SymbianOS')) {
  630. // Reject SymbianOS.
  631. return false;
  632. }
  633. if (strpos($useragent, 'Android')) {
  634. // Reject Androids too.
  635. return false;
  636. }
  637. if (strpos($useragent, 'iPhone') or strpos($useragent, 'iPad') or strpos($useragent, 'iPod')) {
  638. // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
  639. return false;
  640. }
  641. if (strpos($useragent, 'Chrome')) { // Reject chrome browsers - it needs to be tested explicitly.
  642. return false;
  643. }
  644. if (empty($version)) {
  645. return true; // No version specified.
  646. }
  647. if (preg_match("/AppleWebKit\/([0-9.]+)/i", $useragent, $match)) {
  648. if (version_compare($match[1], $version) >= 0) {
  649. return true;
  650. }
  651. }
  652. return false;
  653. }
  654. /**
  655. * Checks the user agent is Chrome
  656. *
  657. * @return bool true if chrome
  658. */
  659. public static function is_chrome() {
  660. return self::check_chrome_version();
  661. }
  662. /**
  663. * Checks the user agent is Chrome based and that the version is equal to or greater than that specified.
  664. *
  665. * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
  666. * @return bool
  667. */
  668. public static function check_chrome_version($version = null) {
  669. // Chrome.
  670. $useragent = self::get_user_agent_string();
  671. if ($useragent === false) {
  672. return false;
  673. }
  674. if (strpos($useragent, 'Chrome') === false) {
  675. return false;
  676. }
  677. if (empty($version)) {
  678. return true; // No version specified.
  679. }
  680. if (preg_match("/Chrome\/(.*)[ ]+/i", $useragent, $match)) {
  681. if (version_compare($match[1], $version) >= 0) {
  682. return true;
  683. }
  684. }
  685. return false;
  686. }
  687. /**
  688. * Checks the user agent is webkit android based.
  689. *
  690. * @return bool true if webkit based and on Android
  691. */
  692. public static function is_webkit_android() {
  693. return self::check_webkit_android_version();
  694. }
  695. /**
  696. * Checks the user agent is Webkit based and on Android and that the version is equal to or greater than that specified.
  697. *
  698. * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
  699. * @return bool
  700. */
  701. public static function check_webkit_android_version($version = null) {
  702. // WebKit browser on Android.
  703. $useragent = self::get_user_agent_string();
  704. if ($useragent === false) {
  705. return false;
  706. }
  707. if (strpos($useragent, 'Linux; U; Android') === false) {
  708. return false;
  709. }
  710. if (empty($version)) {
  711. return true; // No version specified.
  712. }
  713. if (preg_match("/AppleWebKit\/([0-9]+)/i", $useragent, $match)) {
  714. if (version_compare($match[1], $version) >= 0) {
  715. return true;
  716. }
  717. }
  718. return false;
  719. }
  720. /**
  721. * Checks the user agent is Safari on iOS
  722. *
  723. * @return bool true if Safari on iOS
  724. */
  725. public static function is_safari_ios() {
  726. return self::check_safari_ios_version();
  727. }
  728. /**
  729. * Checks the user agent is Safari on iOS and that the version is equal to or greater than that specified.
  730. *
  731. * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
  732. * @return bool
  733. */
  734. public static function check_safari_ios_version($version = null) {
  735. // Safari on iPhone, iPad and iPod touch.
  736. $useragent = self::get_user_agent_string();
  737. if ($useragent === false) {
  738. return false;
  739. }
  740. if (strpos($useragent, 'AppleWebKit') === false or strpos($useragent, 'Safari') === false) {
  741. return false;
  742. }
  743. if (!strpos($useragent, 'iPhone') and !strpos($useragent, 'iPad') and !strpos($useragent, 'iPod')) {
  744. return false;
  745. }
  746. if (empty($version)) {
  747. return true; // No version specified.
  748. }
  749. if (preg_match("/AppleWebKit\/([0-9]+)/i", $useragent, $match)) {
  750. if (version_compare($match[1], $version) >= 0) {
  751. return true;
  752. }
  753. }
  754. return false;
  755. }
  756. /**
  757. * Check if the user agent matches a given brand.
  758. *
  759. * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
  760. *
  761. * @param string $brand
  762. * @return bool
  763. */
  764. public static function check_browser_operating_system($brand) {
  765. $useragent = self::get_user_agent_string();
  766. return ($useragent !== false && preg_match("/$brand/i", $useragent));
  767. }
  768. /**
  769. * Gets an array of CSS classes to represent the user agent.
  770. * @return array
  771. */
  772. public static function get_browser_version_classes() {
  773. $classes = array();
  774. if (self::is_ie()) {
  775. $classes[] = 'ie';
  776. for ($i = 12; $i >= 6; $i--) {
  777. if (self::check_ie_version($i)) {
  778. $classes[] = 'ie'.$i;
  779. break;
  780. }
  781. }
  782. } else if (self::is_firefox() || self::is_gecko() || self::check_camino_version()) {
  783. $classes[] = 'gecko';
  784. if (preg_match('/rv\:([1-2])\.([0-9])/', self::get_user_agent_string(), $matches)) {
  785. $classes[] = "gecko{$matches[1]}{$matches[2]}";
  786. }
  787. } else if (self::is_webkit()) {
  788. $classes[] = 'safari';
  789. if (self::is_safari_ios()) {
  790. $classes[] = 'ios';
  791. } else if (self::is_webkit_android()) {
  792. $classes[] = 'android';
  793. }
  794. } else if (self::is_opera()) {
  795. $classes[] = 'opera';
  796. }
  797. return $classes;
  798. }
  799. /**
  800. * Returns true if the user agent supports the display of SVG images.
  801. *
  802. * @return bool
  803. */
  804. public static function supports_svg() {
  805. // IE 5 - 8 don't support SVG at all.
  806. $instance = self::instance();
  807. if ($instance->supportssvg === null) {
  808. if ($instance->useragent === false) {
  809. // Can't be sure, just say no.
  810. $instance->supportssvg = false;
  811. } else if (self::is_ie() and !self::check_ie_version('9')) {
  812. // IE < 9 doesn't support SVG. Say no.
  813. $instance->supportssvg = false;
  814. } else if (self::is_ie() and !self::check_ie_version('10') and self::check_ie_compatibility_view()) {
  815. // IE 9 Compatibility View doesn't support SVG. Say no.
  816. $instance->supportssvg = false;
  817. } else if (preg_match('#Android +[0-2]\.#', $instance->useragent)) {
  818. // Android < 3 doesn't support SVG. Say no.
  819. $instance->supportssvg = false;
  820. } else if (self::is_opera()) {
  821. // Opera 12 still does not support SVG well enough. Say no.
  822. $instance->supportssvg = false;
  823. } else {
  824. // Presumed fine.
  825. $instance->supportssvg = true;
  826. }
  827. }
  828. return $instance->supportssvg;
  829. }
  830. /**
  831. * Returns true if the user agent supports the MIME media type for JSON text, as defined in RFC 4627.
  832. *
  833. * @return bool
  834. */
  835. public static function supports_json_contenttype() {
  836. // Modern browsers other than IE correctly supports 'application/json' media type.
  837. if (!self::is_ie()) {
  838. return true;
  839. }
  840. // IE8+ supports 'application/json' media type, when NOT in Compatibility View mode.
  841. // Refs:
  842. // - http://blogs.msdn.com/b/ie/archive/2008/09/10/native-json-in-ie8.aspx;
  843. // - MDL-39810: issues when using 'text/plain' in Compatibility View for the body of an HTTP POST response.
  844. if (self::check_ie_version(8) && !self::check_ie_compatibility_view()) {
  845. return true;
  846. }
  847. // This browser does not support json.
  848. return false;
  849. }
  850. }