MDL-36316 useragent: Reduce user agent sniffing
[moodle.git] / lib / classes / useragent.php
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/>.
17 /**
18  * Environment class to aid with the detection and establishment of the working environment.
19  *
20  * @package    core
21  * @copyright  2013 Sam Hemelryk
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 /**
26  * The user agent class.
27  *
28  * It's important to note that we do not like browser sniffing and its use in core code is highly discouraged.
29  * No new uses of this API will be integrated unless there is absolutely no alternative.
30  *
31  * This API supports the few browser checks we do have in core, all of which one day will hopefully be removed.
32  * The API will remain to support any third party use out there, however at some point like all code it will be deprecated.
33  *
34  * Use sparingly and only with good cause!
35  *
36  * @package    core
37  * @copyright  2013 Sam Hemelryk
38  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39  */
40 class core_useragent {
42     /**
43      * The default for devices, think of as a computer.
44      */
45     const DEVICETYPE_DEFAULT = 'default';
46     /**
47      * Legacy devices, or at least legacy browsers. These are older devices/browsers
48      * that don't support standards.
49      */
50     const DEVICETYPE_LEGACY = 'legacy';
51     /**
52      * Mobile devices like your cell phone or hand held gaming device.
53      */
54     const DEVICETYPE_MOBILE = 'mobile';
55     /**
56      * Tables, larger than hand held, but still easily portable and smaller than a laptop.
57      */
58     const DEVICETYPE_TABLET = 'tablet';
60     /**
61      * An instance of this class.
62      * @var core_useragent
63      */
64     protected static $instance = null;
66     /**
67      * The device types we track.
68      * @var array
69      */
70     public static $devicetypes = array(
71         self::DEVICETYPE_DEFAULT,
72         self::DEVICETYPE_LEGACY,
73         self::DEVICETYPE_MOBILE,
74         self::DEVICETYPE_TABLET
75     );
77     /**
78      * The current requests user agent string if there was one.
79      * @var string|bool|null Null until initialised, false if none available, or string when available.
80      */
81     protected $useragent = null;
83     /**
84      * The users device type, one of self::DEVICETYPE_*.
85      * @var string null until initialised
86      */
87     protected $devicetype = null;
89     /**
90      * Custom device types entered into the admin interface.
91      * @var array
92      */
93     protected $devicetypecustoms = array();
95     /**
96      * True if the user agent supports the display of svg images. False if not.
97      * @var bool|null Null until initialised, then true or false.
98      */
99     protected $supportssvg = null;
101     /**
102      * Get an instance of the user agent object.
103      *
104      * @param bool $reload If set to true the user agent will be reset and all ascertations remade.
105      * @param string $forceuseragent The string to force as the user agent, don't use unless absolutely unavoidable.
106      * @return core_useragent
107      */
108     public static function instance($reload = false, $forceuseragent = null) {
109         if (!self::$instance || $reload) {
110             self::$instance = new core_useragent($forceuseragent);
111         }
112         return self::$instance;
113     }
115     /**
116      * Constructs a new user agent object. Publically you must use the instance method above.
117      *
118      * @param string|null $forceuseragent Optional a user agent to force.
119      */
120     protected function __construct($forceuseragent = null) {
121         global $CFG;
122         if (!empty($CFG->devicedetectregex)) {
123             $this->devicetypecustoms = json_decode($CFG->devicedetectregex);
124         }
125         if ($forceuseragent !== null) {
126             $this->useragent = $forceuseragent;
127         } else if (!empty($_SERVER['HTTP_USER_AGENT'])) {
128             $this->useragent = $_SERVER['HTTP_USER_AGENT'];
129         } else {
130             $this->useragent = false;
131             $this->devicetype = self::DEVICETYPE_DEFAULT;
132         }
133     }
135     /**
136      * Returns the user agent string.
137      * @return bool|string The user agent string or false if one isn't available.
138      */
139     public static function get_user_agent_string() {
140         $instance = self::instance();
141         return $instance->useragent;
142     }
144     /**
145      * Returns the device type we believe is being used.
146      * @return string
147      */
148     public static function get_device_type() {
149         $instance = self::instance();
150         if ($instance->devicetype === null) {
151             return $instance->guess_device_type();
152         }
153         return $instance->devicetype;
154     }
156     /**
157      * Guesses the device type the user agent is running on.
158      *
159      * @return string
160      */
161     protected function guess_device_type() {
162         global $CFG;
163         if (empty($CFG->enabledevicedetection)) {
164             $this->devicetype = self::DEVICETYPE_DEFAULT;
165             return $this->devicetype;
166         }
167         foreach ($this->devicetypecustoms as $value => $regex) {
168             if (preg_match($regex, $this->useragent)) {
169                 $this->devicetype = $value;
170                 return $this->devicetype;
171             }
172         }
173         if ($this->is_useragent_mobile()) {
174             $this->devicetype = 'mobile';
175         } else if ($this->is_useragent_tablet()) {
176             $this->devicetype = 'tablet';
177         } else if (substr($this->useragent, 0, 34) === 'Mozilla/4.0 (compatible; MSIE 6.0;') {
178             // Safe way to check for IE6 and not get false positives for some IE 7/8 users.
179             $this->devicetype = 'legacy';
180         } else {
181             $this->devicetype = self::DEVICETYPE_DEFAULT;
182         }
183         return $this->devicetype;
184     }
186     /**
187      * Returns true if the user appears to be on a mobile device.
188      * @return bool
189      */
190     protected function is_useragent_mobile() {
191         // Mobile detection PHP direct copy from open source detectmobilebrowser.com.
192         $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';
193         $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';
194         return (preg_match($phonesregex, $this->useragent) || preg_match($modelsregex, substr($this->useragent, 0, 4)));
195     }
197     /**
198      * Returns true if the user appears to be on a tablet.
199      * @return int
200      */
201     protected function is_useragent_tablet() {
202         $tabletregex = '/Tablet browser|android|iPad|iProd|GT-P1000|GT-I9000|SHW-M180S|SGH-T849|SCH-I800|Build\/ERE27|sholest/i';
203         return (preg_match($tabletregex, $this->useragent));
204     }
206     /**
207      * Gets a list of known device types.
208      *
209      * @param bool $includecustomtypes If set to true we'll include types that have been added by the admin.
210      * @return array
211      */
212     public static function get_device_type_list($includecustomtypes = true) {
213         $types = self::$devicetypes;
214         if ($includecustomtypes) {
215             $instance = self::instance();
216             $types = array_merge($types, array_keys($instance->devicetypecustoms));
217         }
218         return $types;
219     }
221     /**
222      * Returns the theme to use for the given device type.
223      *
224      * This used to be get_selected_theme_for_device_type.
225      * @param null|string $devicetype The device type to find out for. Defaults to the device the user is using,
226      * @return bool
227      */
228     public static function get_device_type_theme($devicetype = null) {
229         global $CFG;
230         if ($devicetype === null) {
231             $devicetype = self::get_device_type();
232         }
233         $themevarname = self::get_device_type_cfg_var_name($devicetype);
234         if (empty($CFG->$themevarname)) {
235             return false;
236         }
237         return $CFG->$themevarname;
238     }
240     /**
241      * Returns the CFG var used to find the theme to use for the given device.
242      *
243      * Used to be get_device_cfg_var_name.
244      *
245      * @param null|string $devicetype The device type to find out for. Defaults to the device the user is using,
246      * @return string
247      */
248     public static function get_device_type_cfg_var_name($devicetype = null) {
249         if ($devicetype == self::DEVICETYPE_DEFAULT || empty($devicetype)) {
250             return 'theme';
251         }
252         return 'theme' . $devicetype;
253     }
255     /**
256      * Gets the device type the user is currently using.
257      * @return string
258      */
259     public static function get_user_device_type() {
260         $device = self::get_device_type();
261         $switched = get_user_preferences('switchdevice'.$device, false);
262         if ($switched != false) {
263             return $switched;
264         }
265         return $device;
266     }
268     /**
269      * Switches the device type we think the user is using to what ever was given.
270      * @param string $newdevice
271      * @return bool
272      * @throws coding_exception
273      */
274     public static function set_user_device_type($newdevice) {
275         $devicetype = self::get_device_type();
276         if ($newdevice == $devicetype) {
277             unset_user_preference('switchdevice'.$devicetype);
278             return true;
279         } else {
280             $devicetypes = self::get_device_type_list();
281             if (in_array($newdevice, $devicetypes)) {
282                 set_user_preference('switchdevice'.$devicetype, $newdevice);
283                 return true;
284             }
285         }
286         throw new coding_exception('Invalid device type provided to set_user_device_type');
287     }
289     /**
290      * Returns true if the user agent matches the given brand and the version is equal to or greater than that specified.
291      *
292      * @param string $brand The branch to check for.
293      * @param scalar $version The version if we need to find out if it is equal to or greater than that specified.
294      * @return bool
295      */
296     public static function check_browser_version($brand, $version = null) {
297         switch ($brand) {
299             case 'MSIE':
300                 // Internet Explorer.
301                 return self::check_ie_version($version);
303             case 'Firefox':
304                 // Mozilla Firefox browsers.
305                 return self::check_firefox_version($version);
307             case 'Chrome':
308                 return self::check_chrome_version($version);
310             case 'Opera':
311                 // Opera.
312                 return self::check_opera_version($version);
314             case 'Safari':
315                 // Desktop version of Apple Safari browser - no mobile or touch devices.
316                 return self::check_safari_version($version);
318             case 'Safari iOS':
319                 // Safari on iPhone, iPad and iPod touch.
320                 return self::check_safari_ios_version($version);
322             case 'WebKit':
323                 // WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles).
324                 return self::check_webkit_version($version);
326             case 'Gecko':
327                 // Gecko based browsers.
328                 return self::check_gecko_version($version);
330             case 'WebKit Android':
331                 // WebKit browser on Android.
332                 return self::check_webkit_android_version($version);
334             case 'Camino':
335                 // OSX browser using Gecke engine.
336                 return self::check_camino_version($version);
337         }
338         // Who knows?! doesn't pass anyway.
339         return false;
340     }
342     /**
343      * Checks the user agent is camino based and that the version is equal to or greater than that specified.
344      *
345      * Camino browser is at the end of its life, its no longer being developed or supported, just don't worry about it.
346      *
347      * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
348      * @return bool
349      */
350     protected static function check_camino_version($version = null) {
351         // OSX browser using Gecko engine.
352         $useragent = self::get_user_agent_string();
353         if ($useragent === false) {
354             return false;
355         }
356         if (strpos($useragent, 'Camino') === false) {
357             return false;
358         }
359         if (empty($version)) {
360             return true; // No version specified.
361         }
362         if (preg_match("/Camino\/([0-9\.]+)/i", $useragent, $match)) {
363             if (version_compare($match[1], $version) >= 0) {
364                 return true;
365             }
366         }
367         return false;
368     }
370     /**
371      * Checks the user agent is Firefox (of any version).
372      *
373      * @return bool true if firefox
374      */
375     public static function is_firefox() {
376         return self::check_firefox_version();
377     }
379     /**
380      * Checks the user agent is Firefox based and that the version is equal to or greater than that specified.
381      *
382      * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
383      * @return bool
384      */
385     public static function check_firefox_version($version = null) {
386         // Mozilla Firefox browsers.
387         $useragent = self::get_user_agent_string();
388         if ($useragent === false) {
389             return false;
390         }
391         if (strpos($useragent, 'Firefox') === false && strpos($useragent, 'Iceweasel') === false) {
392             return false;
393         }
394         if (empty($version)) {
395             return true; // No version specified..
396         }
397         if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $useragent, $match)) {
398             if (version_compare($match[2], $version) >= 0) {
399                 return true;
400             }
401         }
402         return false;
403     }
405     /**
406      * Checks the user agent is Gecko based (of any version).
407      *
408      * @return bool true if Gecko based.
409      */
410     public static function is_gecko() {
411         return self::check_gecko_version();
412     }
414     /**
415      * Checks the user agent is Gecko based and that the version is equal to or greater than that specified.
416      *
417      * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
418      * @return bool
419      */
420     public static function check_gecko_version($version = null) {
421         // Gecko based browsers.
422         // Do not look for dates any more, we expect real Firefox version here.
423         $useragent = self::get_user_agent_string();
424         if ($useragent === false) {
425             return false;
426         }
427         if (empty($version)) {
428             $version = 1;
429         } else if ($version > 20000000) {
430             // This is just a guess, it is not supposed to be 100% accurate!
431             if (preg_match('/^201/', $version)) {
432                 $version = 3.6;
433             } else if (preg_match('/^200[7-9]/', $version)) {
434                 $version = 3;
435             } else if (preg_match('/^2006/', $version)) {
436                 $version = 2;
437             } else {
438                 $version = 1.5;
439             }
440         }
441         if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $useragent, $match)) {
442             // Use real Firefox version if specified in user agent string.
443             if (version_compare($match[2], $version) >= 0) {
444                 return true;
445             }
446         } else if (preg_match("/Gecko\/([0-9\.]+)/i", $useragent, $match)) {
447             // Gecko might contain date or Firefox revision, let's just guess the Firefox version from the date.
448             $browserver = $match[1];
449             if ($browserver > 20000000) {
450                 // This is just a guess, it is not supposed to be 100% accurate!
451                 if (preg_match('/^201/', $browserver)) {
452                     $browserver = 3.6;
453                 } else if (preg_match('/^200[7-9]/', $browserver)) {
454                     $browserver = 3;
455                 } else if (preg_match('/^2006/', $version)) {
456                     $browserver = 2;
457                 } else {
458                     $browserver = 1.5;
459                 }
460             }
461             if (version_compare($browserver, $version) >= 0) {
462                 return true;
463             }
464         }
465         return false;
466     }
468     /**
469      * Checks the user agent is IE (of any version).
470      *
471      * @return bool true if internet exporeer
472      */
473     public static function is_ie() {
474         return self::check_ie_version();
475     }
477     /**
478      * Checks the user agent is IE and that the version is equal to or greater than that specified.
479      *
480      * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
481      * @return bool
482      */
483     public static function check_ie_version($version = null) {
484         // Internet Explorer.
485         $useragent = self::get_user_agent_string();
486         if ($useragent === false) {
487             return false;
488         }
489         if (strpos($useragent, 'Opera') !== false) {
490             // Reject Opera.
491             return false;
492         }
493         // In case of IE we have to deal with BC of the version parameter.
494         if (is_null($version)) {
495             $version = 5.5; // Anything older is not considered a browser at all!
496         }
497         // IE uses simple versions, let's cast it to float to simplify the logic here.
498         $version = round($version, 1);
499         // See: http://www.useragentstring.com/pages/Internet%20Explorer/.
500         if (preg_match("/MSIE ([0-9\.]+)/", $useragent, $match)) {
501             $browser = $match[1];
502         } else {
503             return false;
504         }
505         // IE8 and later versions may pretend to be IE7 for intranet sites, use Trident version instead,
506         // the Trident should always describe the capabilities of IE in any emulation mode.
507         if ($browser === '7.0' and preg_match("/Trident\/([0-9\.]+)/", $useragent, $match)) {
508             $browser = $match[1] + 4; // NOTE: Hopefully this will work also for future IE versions.
509         }
510         $browser = round($browser, 1);
511         return ($browser >= $version);
512     }
514     /**
515      * Checks the user agent is Opera (of any version).
516      *
517      * @return bool true if opera
518      */
519     public static function is_opera() {
520         return self::check_opera_version();
521     }
523     /**
524      * Checks the user agent is Opera and that the version is equal to or greater than that specified.
525      *
526      * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
527      * @return bool
528      */
529     public static function check_opera_version($version = null) {
530         // Opera.
531         $useragent = self::get_user_agent_string();
532         if ($useragent === false) {
533             return false;
534         }
535         if (strpos($useragent, 'Opera') === false) {
536             return false;
537         }
538         if (empty($version)) {
539             return true; // No version specified.
540         }
541         // Recent Opera useragents have Version/ with the actual version, e.g.:
542         // Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.289 Version/12.01
543         // That's Opera 12.01, not 9.8.
544         if (preg_match("/Version\/([0-9\.]+)/i", $useragent, $match)) {
545             if (version_compare($match[1], $version) >= 0) {
546                 return true;
547             }
548         } else if (preg_match("/Opera\/([0-9\.]+)/i", $useragent, $match)) {
549             if (version_compare($match[1], $version) >= 0) {
550                 return true;
551             }
552         }
553         return false;
554     }
556     /**
557      * Checks the user agent is webkit based
558      *
559      * @return bool true if webkit
560      */
561     public static function is_webkit() {
562         return self::check_webkit_version();
563     }
565     /**
566      * Checks the user agent is Webkit based and that the version is equal to or greater than that specified.
567      *
568      * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
569      * @return bool
570      */
571     public static function check_webkit_version($version = null) {
572         // WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles).
573         $useragent = self::get_user_agent_string();
574         if ($useragent === false) {
575             return false;
576         }
577         if (strpos($useragent, 'AppleWebKit') === false) {
578             return false;
579         }
580         if (empty($version)) {
581             return true; // No version specified.
582         }
583         if (preg_match("/AppleWebKit\/([0-9.]+)/i", $useragent, $match)) {
584             if (version_compare($match[1], $version) >= 0) {
585                 return true;
586             }
587         }
588         return false;
589     }
591     /**
592      * Checks the user agent is Safari
593      *
594      * @return bool true if safari
595      */
596     public static function is_safari() {
597         return self::check_safari_version();
598     }
600     /**
601      * Checks the user agent is Safari based and that the version is equal to or greater than that specified.
602      *
603      * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
604      * @return bool
605      */
606     public static function check_safari_version($version = null) {
607         // Desktop version of Apple Safari browser - no mobile or touch devices.
608         $useragent = self::get_user_agent_string();
609         if ($useragent === false) {
610             return false;
611         }
612         if (strpos($useragent, 'AppleWebKit') === false) {
613             return false;
614         }
615         // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices.
616         if (strpos($useragent, 'OmniWeb')) {
617             // Reject OmniWeb.
618             return false;
619         }
620         if (strpos($useragent, 'Shiira')) {
621             // Reject Shiira.
622             return false;
623         }
624         if (strpos($useragent, 'SymbianOS')) {
625             // Reject SymbianOS.
626             return false;
627         }
628         if (strpos($useragent, 'Android')) {
629             // Reject Androids too.
630             return false;
631         }
632         if (strpos($useragent, 'iPhone') or strpos($useragent, 'iPad') or strpos($useragent, 'iPod')) {
633             // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
634             return false;
635         }
636         if (strpos($useragent, 'Chrome')) { // Reject chrome browsers - it needs to be tested explicitly.
637             return false;
638         }
640         if (empty($version)) {
641             return true; // No version specified.
642         }
643         if (preg_match("/AppleWebKit\/([0-9.]+)/i", $useragent, $match)) {
644             if (version_compare($match[1], $version) >= 0) {
645                 return true;
646             }
647         }
648         return false;
649     }
651     /**
652      * Checks the user agent is Chrome
653      *
654      * @return bool true if chrome
655      */
656     public static function is_chrome() {
657         return self::check_chrome_version();
658     }
660     /**
661      * Checks the user agent is Chrome based and that the version is equal to or greater than that specified.
662      *
663      * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
664      * @return bool
665      */
666     public static function check_chrome_version($version = null) {
667         // Chrome.
668         $useragent = self::get_user_agent_string();
669         if ($useragent === false) {
670             return false;
671         }
672         if (strpos($useragent, 'Chrome') === false) {
673             return false;
674         }
675         if (empty($version)) {
676             return true; // No version specified.
677         }
678         if (preg_match("/Chrome\/(.*)[ ]+/i", $useragent, $match)) {
679             if (version_compare($match[1], $version) >= 0) {
680                 return true;
681             }
682         }
683         return false;
684     }
686     /**
687      * Checks the user agent is webkit android based.
688      *
689      * @return bool true if webkit based and on Android
690      */
691     public static function is_webkit_android() {
692         return self::check_webkit_android_version();
693     }
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     }
721     /**
722      * Checks the user agent is Safari on iOS
723      *
724      * @return bool true if Safari on iOS
725      */
726     public static function is_safari_ios() {
727         return self::check_safari_ios_version();
728     }
730     /**
731      * Checks the user agent is Safari on iOS and that the version is equal to or greater than that specified.
732      *
733      * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
734      * @return bool
735      */
736     public static function check_safari_ios_version($version = null) {
737         // Safari on iPhone, iPad and iPod touch.
738         $useragent = self::get_user_agent_string();
739         if ($useragent === false) {
740             return false;
741         }
742         if (strpos($useragent, 'AppleWebKit') === false or strpos($useragent, 'Safari') === false) {
743             return false;
744         }
745         if (!strpos($useragent, 'iPhone') and !strpos($useragent, 'iPad') and !strpos($useragent, 'iPod')) {
746             return false;
747         }
748         if (empty($version)) {
749             return true; // No version specified.
750         }
751         if (preg_match("/AppleWebKit\/([0-9]+)/i", $useragent, $match)) {
752             if (version_compare($match[1], $version) >= 0) {
753                 return true;
754             }
755         }
756         return false;
757     }
759     /**
760      * Check if the user agent matches a given brand.
761      *
762      * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
763      *
764      * @param string $brand
765      * @return bool
766      */
767     public static function check_browser_operating_system($brand) {
768         $useragent = self::get_user_agent_string();
769         return ($useragent !== false && preg_match("/$brand/i", $useragent));
770     }
772     /**
773      * Gets an array of CSS classes to represent the user agent.
774      * @return array
775      */
776     public static function get_browser_version_classes() {
777         $classes = array();
778         if (self::is_ie()) {
779             $classes[] = 'ie';
780             for ($i = 12; $i >= 6; $i--) {
781                 if (self::check_ie_version($i)) {
782                     $classes[] = 'ie'.$i;
783                     break;
784                 }
785             }
786         } else if (self::is_firefox() || self::is_gecko() || self::check_camino_version()) {
787             $classes[] = 'gecko';
788             if (preg_match('/rv\:([1-2])\.([0-9])/', self::get_user_agent_string(), $matches)) {
789                 $classes[] = "gecko{$matches[1]}{$matches[2]}";
790             }
791         } else if (self::is_webkit()) {
792             $classes[] = 'safari';
793             if (self::is_safari_ios()) {
794                 $classes[] = 'ios';
795             } else if (self::is_webkit_android()) {
796                 $classes[] = 'android';
797             }
798         } else if (self::is_opera()) {
799             $classes[] = 'opera';
800         }
801         return $classes;
802     }
804     /**
805      * Returns true if the user agent supports the display of SVG images.
806      *
807      * @return bool
808      */
809     public static function supports_svg() {
810         // IE 5 - 8 don't support SVG at all.
811         $instance = self::instance();
812         if ($instance->supportssvg === null) {
813             if ($instance->useragent === false) {
814                 // Can't be sure, just say no.
815                 $instance->supportssvg = false;
816             } else if (self::is_ie() and !self::check_ie_version('9')) {
817                 // IE < 9 doesn't support SVG. Say no.
818                 $instance->supportssvg = false;
819             } else if (preg_match('#Android +[0-2]\.#', $instance->useragent)) {
820                 // Android < 3 doesn't support SVG. Say no.
821                 $instance->supportssvg = false;
822             } else if (self::is_opera()) {
823                 // Opera 12 still does not support SVG well enough. Say no.
824                 $instance->supportssvg = false;
825             } else {
826                 // Presumed fine.
827                 $instance->supportssvg = true;
828             }
829         }
830         return $instance->supportssvg;
831     }