source: moodle/trunk/fuentes/lib/classes/useragent.php @ 1331

Last change on this file since 1331 was 1331, checked in by jrpelegrina, 3 years ago

Updated to moodle 3.0.3

File size: 36.8 KB
Line 
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/**
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 */
24
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 */
40class core_useragent {
41
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';
59
60    /**
61     * An instance of this class.
62     * @var core_useragent
63     */
64    protected static $instance = null;
65
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    );
76
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;
82
83    /**
84     * The users device type, one of self::DEVICETYPE_*.
85     * @var string null until initialised
86     */
87    protected $devicetype = null;
88
89    /**
90     * Custom device types entered into the admin interface.
91     * @var array
92     */
93    protected $devicetypecustoms = array();
94
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;
100
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    }
114
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, true);
124        }
125        if ($this->devicetypecustoms === null) {
126            // This shouldn't happen unless you're hardcoding the config value.
127            debugging('Config devicedetectregex is not valid JSON object');
128            $this->devicetypecustoms = array();
129        }
130        if ($forceuseragent !== null) {
131            $this->useragent = $forceuseragent;
132        } else if (!empty($_SERVER['HTTP_USER_AGENT'])) {
133            $this->useragent = $_SERVER['HTTP_USER_AGENT'];
134        } else {
135            $this->useragent = false;
136            $this->devicetype = self::DEVICETYPE_DEFAULT;
137        }
138    }
139
140    /**
141     * Returns the user agent string.
142     * @return bool|string The user agent string or false if one isn't available.
143     */
144    public static function get_user_agent_string() {
145        $instance = self::instance();
146        return $instance->useragent;
147    }
148
149    /**
150     * Returns the device type we believe is being used.
151     * @return string
152     */
153    public static function get_device_type() {
154        $instance = self::instance();
155        if ($instance->devicetype === null) {
156            return $instance->guess_device_type();
157        }
158        return $instance->devicetype;
159    }
160
161    /**
162     * Guesses the device type the user agent is running on.
163     *
164     * @return string
165     */
166    protected function guess_device_type() {
167        global $CFG;
168        if (empty($CFG->enabledevicedetection)) {
169            $this->devicetype = self::DEVICETYPE_DEFAULT;
170            return $this->devicetype;
171        }
172        foreach ($this->devicetypecustoms as $value => $regex) {
173            if (preg_match($regex, $this->useragent)) {
174                $this->devicetype = $value;
175                return $this->devicetype;
176            }
177        }
178        if ($this->is_useragent_mobile()) {
179            $this->devicetype = self::DEVICETYPE_MOBILE;
180        } else if ($this->is_useragent_tablet()) {
181            $this->devicetype = self::DEVICETYPE_TABLET;
182        } else if (self::check_ie_version('0') && !self::check_ie_version('7.0')) {
183            // IE 6 and before are considered legacy.
184            $this->devicetype = self::DEVICETYPE_LEGACY;
185        } else {
186            $this->devicetype = self::DEVICETYPE_DEFAULT;
187        }
188        return $this->devicetype;
189    }
190
191    /**
192     * Returns true if the user appears to be on a mobile device.
193     * @return bool
194     */
195    protected function is_useragent_mobile() {
196        // Mobile detection PHP direct copy from open source detectmobilebrowser.com.
197        $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';
198        $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';
199        return (preg_match($phonesregex, $this->useragent) || preg_match($modelsregex, substr($this->useragent, 0, 4)));
200    }
201
202    /**
203     * Returns true if the user appears to be on a tablet.
204     *
205     * @return int
206     */
207    protected function is_useragent_tablet() {
208        $tabletregex = '/Tablet browser|android|iPad|iProd|GT-P1000|GT-I9000|SHW-M180S|SGH-T849|SCH-I800|Build\/ERE27|sholest/i';
209        return (preg_match($tabletregex, $this->useragent));
210    }
211
212    /**
213     * Whether the user agent relates to a web crawler.
214     * This includes all types of web crawler.
215     * @return bool
216     */
217    protected function is_useragent_web_crawler() {
218        $regex = '/Googlebot|google\.com|Yahoo! Slurp|\[ZSEBOT\]|msnbot|bingbot|BingPreview|Yandex|AltaVista|Baiduspider|Teoma/i';
219        return (preg_match($regex, $this->useragent));
220    }
221
222    /**
223     * Gets a list of known device types.
224     *
225     * @param bool $includecustomtypes If set to true we'll include types that have been added by the admin.
226     * @return array
227     */
228    public static function get_device_type_list($includecustomtypes = true) {
229        $types = self::$devicetypes;
230        if ($includecustomtypes) {
231            $instance = self::instance();
232            $types = array_merge($types, array_keys($instance->devicetypecustoms));
233        }
234        return $types;
235    }
236
237    /**
238     * Returns the theme to use for the given device type.
239     *
240     * This used to be get_selected_theme_for_device_type.
241     * @param null|string $devicetype The device type to find out for. Defaults to the device the user is using,
242     * @return bool
243     */
244    public static function get_device_type_theme($devicetype = null) {
245        global $CFG;
246        if ($devicetype === null) {
247            $devicetype = self::get_device_type();
248        }
249        $themevarname = self::get_device_type_cfg_var_name($devicetype);
250        if (empty($CFG->$themevarname)) {
251            return false;
252        }
253        return $CFG->$themevarname;
254    }
255
256    /**
257     * Returns the CFG var used to find the theme to use for the given device.
258     *
259     * Used to be get_device_cfg_var_name.
260     *
261     * @param null|string $devicetype The device type to find out for. Defaults to the device the user is using,
262     * @return string
263     */
264    public static function get_device_type_cfg_var_name($devicetype = null) {
265        if ($devicetype == self::DEVICETYPE_DEFAULT || empty($devicetype)) {
266            return 'theme';
267        }
268        return 'theme' . $devicetype;
269    }
270
271    /**
272     * Gets the device type the user is currently using.
273     * @return string
274     */
275    public static function get_user_device_type() {
276        $device = self::get_device_type();
277        $switched = get_user_preferences('switchdevice'.$device, false);
278        if ($switched != false) {
279            return $switched;
280        }
281        return $device;
282    }
283
284    /**
285     * Switches the device type we think the user is using to what ever was given.
286     * @param string $newdevice
287     * @return bool
288     * @throws coding_exception
289     */
290    public static function set_user_device_type($newdevice) {
291        $devicetype = self::get_device_type();
292        if ($newdevice == $devicetype) {
293            unset_user_preference('switchdevice'.$devicetype);
294            return true;
295        } else {
296            $devicetypes = self::get_device_type_list();
297            if (in_array($newdevice, $devicetypes)) {
298                set_user_preference('switchdevice'.$devicetype, $newdevice);
299                return true;
300            }
301        }
302        throw new coding_exception('Invalid device type provided to set_user_device_type');
303    }
304
305    /**
306     * Returns true if the user agent matches the given brand and the version is equal to or greater than that specified.
307     *
308     * @param string $brand The branch to check for.
309     * @param scalar $version The version if we need to find out if it is equal to or greater than that specified.
310     * @return bool
311     */
312    public static function check_browser_version($brand, $version = null) {
313        switch ($brand) {
314
315            case 'MSIE':
316                // Internet Explorer.
317                return self::check_ie_version($version);
318
319            case 'Firefox':
320                // Mozilla Firefox browsers.
321                return self::check_firefox_version($version);
322
323            case 'Chrome':
324                return self::check_chrome_version($version);
325
326            case 'Opera':
327                // Opera.
328                return self::check_opera_version($version);
329
330            case 'Safari':
331                // Desktop version of Apple Safari browser - no mobile or touch devices.
332                return self::check_safari_version($version);
333
334            case 'Safari iOS':
335                // Safari on iPhone, iPad and iPod touch.
336                return self::check_safari_ios_version($version);
337
338            case 'WebKit':
339                // WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles).
340                return self::check_webkit_version($version);
341
342            case 'Gecko':
343                // Gecko based browsers.
344                return self::check_gecko_version($version);
345
346            case 'WebKit Android':
347                // WebKit browser on Android.
348                return self::check_webkit_android_version($version);
349
350            case 'Camino':
351                // OSX browser using Gecke engine.
352                return self::check_camino_version($version);
353        }
354        // Who knows?! doesn't pass anyway.
355        return false;
356    }
357
358    /**
359     * Checks the user agent is camino based and that the version is equal to or greater than that specified.
360     *
361     * Camino browser is at the end of its life, its no longer being developed or supported, just don't worry about it.
362     *
363     * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
364     * @return bool
365     */
366    protected static function check_camino_version($version = null) {
367        // OSX browser using Gecko engine.
368        $useragent = self::get_user_agent_string();
369        if ($useragent === false) {
370            return false;
371        }
372        if (strpos($useragent, 'Camino') === false) {
373            return false;
374        }
375        if (empty($version)) {
376            return true; // No version specified.
377        }
378        if (preg_match("/Camino\/([0-9\.]+)/i", $useragent, $match)) {
379            if (version_compare($match[1], $version) >= 0) {
380                return true;
381            }
382        }
383        return false;
384    }
385
386    /**
387     * Checks the user agent is Firefox (of any version).
388     *
389     * @return bool true if firefox
390     */
391    public static function is_firefox() {
392        return self::check_firefox_version();
393    }
394
395    /**
396     * Checks the user agent is Firefox based and that the version is equal to or greater than that specified.
397     *
398     * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
399     * @return bool
400     */
401    public static function check_firefox_version($version = null) {
402        // Mozilla Firefox browsers.
403        $useragent = self::get_user_agent_string();
404        if ($useragent === false) {
405            return false;
406        }
407        if (strpos($useragent, 'Firefox') === false && strpos($useragent, 'Iceweasel') === false) {
408            return false;
409        }
410        if (empty($version)) {
411            return true; // No version specified..
412        }
413        if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $useragent, $match)) {
414            if (version_compare($match[2], $version) >= 0) {
415                return true;
416            }
417        }
418        return false;
419    }
420
421    /**
422     * Checks the user agent is Gecko based (of any version).
423     *
424     * @return bool true if Gecko based.
425     */
426    public static function is_gecko() {
427        return self::check_gecko_version();
428    }
429
430    /**
431     * Checks the user agent is Gecko based and that the version is equal to or greater than that specified.
432     *
433     * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
434     * @return bool
435     */
436    public static function check_gecko_version($version = null) {
437        // Gecko based browsers.
438        // Do not look for dates any more, we expect real Firefox version here.
439        $useragent = self::get_user_agent_string();
440        if ($useragent === false) {
441            return false;
442        }
443        if (empty($version)) {
444            $version = 1;
445        } else if ($version > 20000000) {
446            // This is just a guess, it is not supposed to be 100% accurate!
447            if (preg_match('/^201/', $version)) {
448                $version = 3.6;
449            } else if (preg_match('/^200[7-9]/', $version)) {
450                $version = 3;
451            } else if (preg_match('/^2006/', $version)) {
452                $version = 2;
453            } else {
454                $version = 1.5;
455            }
456        }
457        if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $useragent, $match)) {
458            // Use real Firefox version if specified in user agent string.
459            if (version_compare($match[2], $version) >= 0) {
460                return true;
461            }
462        } else if (preg_match("/Gecko\/([0-9\.]+)/i", $useragent, $match)) {
463            // Gecko might contain date or Firefox revision, let's just guess the Firefox version from the date.
464            $browserver = $match[1];
465            if ($browserver > 20000000) {
466                // This is just a guess, it is not supposed to be 100% accurate!
467                if (preg_match('/^201/', $browserver)) {
468                    $browserver = 3.6;
469                } else if (preg_match('/^200[7-9]/', $browserver)) {
470                    $browserver = 3;
471                } else if (preg_match('/^2006/', $version)) {
472                    $browserver = 2;
473                } else {
474                    $browserver = 1.5;
475                }
476            }
477            if (version_compare($browserver, $version) >= 0) {
478                return true;
479            }
480        }
481        return false;
482    }
483
484    /**
485     * Checks the user agent is Edge (of any version).
486     *
487     * @return bool true if Edge
488     */
489    public static function is_edge() {
490        return self::check_edge_version();
491    }
492
493    /**
494     * Check the User Agent for the version of Edge.
495     *
496     * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
497     * @return bool
498     */
499    public static function check_edge_version($version = null) {
500        $useragent = self::get_user_agent_string();
501
502        if ($useragent === false) {
503            // No User Agent found.
504            return false;
505        }
506
507        if (strpos($useragent, 'Edge/') === false) {
508            // Edge was not found in the UA - this is not Edge.
509            return false;
510        }
511
512        if (empty($version)) {
513            // No version to check.
514            return true;
515        }
516
517        // Find the version.
518        // Edge versions are always in the format:
519        //      Edge/<version>.<OS build number>
520        preg_match('%Edge/([\d]+)\.(.*)$%', $useragent, $matches);
521
522        // Just to be safe, round the version being tested.
523        // Edge only uses integer versions - the second component is the OS build number.
524        $version = round($version);
525
526        // Check whether the version specified is >= the version found.
527        return version_compare($matches[1], $version, '>=');
528    }
529
530    /**
531     * Checks the user agent is IE (of any version).
532     *
533     * @return bool true if internet exporeer
534     */
535    public static function is_ie() {
536        return self::check_ie_version();
537    }
538
539    /**
540     * Checks the user agent is IE and returns its main properties:
541     * - browser version;
542     * - whether running in compatibility view.
543     *
544     * @return bool|array False if not IE, otherwise an associative array of properties.
545     */
546    public static function check_ie_properties() {
547        // Internet Explorer.
548        $useragent = self::get_user_agent_string();
549        if ($useragent === false) {
550            return false;
551        }
552        if (strpos($useragent, 'Opera') !== false) {
553            // Reject Opera.
554            return false;
555        }
556        // See: http://www.useragentstring.com/pages/Internet%20Explorer/.
557        if (preg_match("/MSIE ([0-9\.]+)/", $useragent, $match)) {
558            $browser = $match[1];
559        // See: http://msdn.microsoft.com/en-us/library/ie/bg182625%28v=vs.85%29.aspx for IE11+ useragent details.
560        } else if (preg_match("/Trident\/[0-9\.]+/", $useragent) && preg_match("/rv:([0-9\.]+)/", $useragent, $match)) {
561            $browser = $match[1];
562        } else {
563            return false;
564        }
565
566        $compatview = false;
567        // IE8 and later versions may pretend to be IE7 for intranet sites, use Trident version instead,
568        // the Trident should always describe the capabilities of IE in any emulation mode.
569        if ($browser === '7.0' and preg_match("/Trident\/([0-9\.]+)/", $useragent, $match)) {
570            $compatview = true;
571            $browser = $match[1] + 4; // NOTE: Hopefully this will work also for future IE versions.
572        }
573        $browser = round($browser, 1);
574        return array(
575            'version'    => $browser,
576            'compatview' => $compatview
577        );
578    }
579
580    /**
581     * Checks the user agent is IE and that the version is equal to or greater than that specified.
582     *
583     * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
584     * @return bool
585     */
586    public static function check_ie_version($version = null) {
587        // Internet Explorer.
588        $properties = self::check_ie_properties();
589        if (!is_array($properties)) {
590            return false;
591        }
592        // In case of IE we have to deal with BC of the version parameter.
593        if (is_null($version)) {
594            $version = 5.5; // Anything older is not considered a browser at all!
595        }
596        // IE uses simple versions, let's cast it to float to simplify the logic here.
597        $version = round($version, 1);
598        return ($properties['version'] >= $version);
599    }
600
601    /**
602     * Checks the user agent is IE and that IE is running under Compatibility View setting.
603     *
604     * @return bool true if internet explorer runs in Compatibility View mode.
605     */
606    public static function check_ie_compatibility_view() {
607        // IE User Agent string when in Compatibility View:
608        // - IE  8: "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/4.0; ...)".
609        // - IE  9: "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/5.0; ...)".
610        // - IE 10: "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/6.0; ...)".
611        // - IE 11: "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0; ...)".
612        // Refs:
613        // - http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx.
614        // - http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx.
615        // - http://blogs.msdn.com/b/ie/archive/2011/04/15/the-ie10-user-agent-string.aspx.
616        // - http://msdn.microsoft.com/en-us/library/ie/hh869301%28v=vs.85%29.aspx.
617        $properties = self::check_ie_properties();
618        if (!is_array($properties)) {
619            return false;
620        }
621        return $properties['compatview'];
622    }
623
624    /**
625     * Checks the user agent is Opera (of any version).
626     *
627     * @return bool true if opera
628     */
629    public static function is_opera() {
630        return self::check_opera_version();
631    }
632
633    /**
634     * Checks the user agent is Opera and that the version is equal to or greater than that specified.
635     *
636     * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
637     * @return bool
638     */
639    public static function check_opera_version($version = null) {
640        // Opera.
641        $useragent = self::get_user_agent_string();
642        if ($useragent === false) {
643            return false;
644        }
645        if (strpos($useragent, 'Opera') === false) {
646            return false;
647        }
648        if (empty($version)) {
649            return true; // No version specified.
650        }
651        // Recent Opera useragents have Version/ with the actual version, e.g.:
652        // Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.289 Version/12.01
653        // That's Opera 12.01, not 9.8.
654        if (preg_match("/Version\/([0-9\.]+)/i", $useragent, $match)) {
655            if (version_compare($match[1], $version) >= 0) {
656                return true;
657            }
658        } else if (preg_match("/Opera\/([0-9\.]+)/i", $useragent, $match)) {
659            if (version_compare($match[1], $version) >= 0) {
660                return true;
661            }
662        }
663        return false;
664    }
665
666    /**
667     * Checks the user agent is webkit based
668     *
669     * @return bool true if webkit
670     */
671    public static function is_webkit() {
672        return self::check_webkit_version();
673    }
674
675    /**
676     * Checks the user agent is Webkit based and that the version is equal to or greater than that specified.
677     *
678     * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
679     * @return bool
680     */
681    public static function check_webkit_version($version = null) {
682        // WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles).
683        $useragent = self::get_user_agent_string();
684        if ($useragent === false) {
685            return false;
686        }
687        if (strpos($useragent, 'AppleWebKit') === false) {
688            return false;
689        }
690        if (empty($version)) {
691            return true; // No version specified.
692        }
693        if (preg_match("/AppleWebKit\/([0-9.]+)/i", $useragent, $match)) {
694            if (version_compare($match[1], $version) >= 0) {
695                return true;
696            }
697        }
698        return false;
699    }
700
701    /**
702     * Checks the user agent is Safari
703     *
704     * @return bool true if safari
705     */
706    public static function is_safari() {
707        return self::check_safari_version();
708    }
709
710    /**
711     * Checks the user agent is Safari based and that the version is equal to or greater than that specified.
712     *
713     * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
714     * @return bool
715     */
716    public static function check_safari_version($version = null) {
717        // Desktop version of Apple Safari browser - no mobile or touch devices.
718        $useragent = self::get_user_agent_string();
719        if ($useragent === false) {
720            return false;
721        }
722        if (strpos($useragent, 'AppleWebKit') === false) {
723            return false;
724        }
725        // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices.
726        if (strpos($useragent, 'OmniWeb')) {
727            // Reject OmniWeb.
728            return false;
729        }
730        if (strpos($useragent, 'Shiira')) {
731            // Reject Shiira.
732            return false;
733        }
734        if (strpos($useragent, 'SymbianOS')) {
735            // Reject SymbianOS.
736            return false;
737        }
738        if (strpos($useragent, 'Android')) {
739            // Reject Androids too.
740            return false;
741        }
742        if (strpos($useragent, 'iPhone') or strpos($useragent, 'iPad') or strpos($useragent, 'iPod')) {
743            // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc.
744            return false;
745        }
746        if (strpos($useragent, 'Chrome')) {
747            // Reject chrome browsers - it needs to be tested explicitly.
748            // This will also reject Edge, which pretends to be both Chrome, and Safari.
749            return false;
750        }
751
752        if (empty($version)) {
753            return true; // No version specified.
754        }
755        if (preg_match("/AppleWebKit\/([0-9.]+)/i", $useragent, $match)) {
756            if (version_compare($match[1], $version) >= 0) {
757                return true;
758            }
759        }
760        return false;
761    }
762
763    /**
764     * Checks the user agent is Chrome
765     *
766     * @return bool true if chrome
767     */
768    public static function is_chrome() {
769        return self::check_chrome_version();
770    }
771
772    /**
773     * Checks the user agent is Chrome based and that the version is equal to or greater than that specified.
774     *
775     * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
776     * @return bool
777     */
778    public static function check_chrome_version($version = null) {
779        // Chrome.
780        $useragent = self::get_user_agent_string();
781        if ($useragent === false) {
782            return false;
783        }
784        if (strpos($useragent, 'Chrome') === false) {
785            return false;
786        }
787        if (empty($version)) {
788            return true; // No version specified.
789        }
790        if (preg_match("/Chrome\/(.*)[ ]+/i", $useragent, $match)) {
791            if (version_compare($match[1], $version) >= 0) {
792                return true;
793            }
794        }
795        return false;
796    }
797
798    /**
799     * Checks the user agent is webkit android based.
800     *
801     * @return bool true if webkit based and on Android
802     */
803    public static function is_webkit_android() {
804        return self::check_webkit_android_version();
805    }
806
807    /**
808     * Checks the user agent is Webkit based and on Android and that the version is equal to or greater than that specified.
809     *
810     * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
811     * @return bool
812     */
813    public static function check_webkit_android_version($version = null) {
814        // WebKit browser on Android.
815        $useragent = self::get_user_agent_string();
816        if ($useragent === false) {
817            return false;
818        }
819        if (strpos($useragent, 'Android') === false) {
820            return false;
821        }
822        if (empty($version)) {
823            return true; // No version specified.
824        }
825        if (preg_match("/AppleWebKit\/([0-9]+)/i", $useragent, $match)) {
826            if (version_compare($match[1], $version) >= 0) {
827                return true;
828            }
829        }
830        return false;
831    }
832
833    /**
834     * Checks the user agent is Safari on iOS
835     *
836     * @return bool true if Safari on iOS
837     */
838    public static function is_safari_ios() {
839        return self::check_safari_ios_version();
840    }
841
842    /**
843     * Checks the user agent is Safari on iOS and that the version is equal to or greater than that specified.
844     *
845     * @param string|int $version A version to check for, returns true if its equal to or greater than that specified.
846     * @return bool
847     */
848    public static function check_safari_ios_version($version = null) {
849        // Safari on iPhone, iPad and iPod touch.
850        $useragent = self::get_user_agent_string();
851        if ($useragent === false) {
852            return false;
853        }
854        if (strpos($useragent, 'AppleWebKit') === false or strpos($useragent, 'Safari') === false) {
855            return false;
856        }
857        if (!strpos($useragent, 'iPhone') and !strpos($useragent, 'iPad') and !strpos($useragent, 'iPod')) {
858            return false;
859        }
860        if (empty($version)) {
861            return true; // No version specified.
862        }
863        if (preg_match("/AppleWebKit\/([0-9]+)/i", $useragent, $match)) {
864            if (version_compare($match[1], $version) >= 0) {
865                return true;
866            }
867        }
868        return false;
869    }
870
871    /**
872     * Checks if the user agent is MS Word.
873     * Not perfect, as older versions of Word use standard IE6/7 user agents without any identifying traits.
874     *
875     * @return bool true if user agent could be identified as MS Word.
876     */
877    public static function is_msword() {
878        $useragent = self::get_user_agent_string();
879        if (!preg_match('/(\bWord\b|ms-office|MSOffice|Microsoft Office)/i', $useragent)) {
880            return false;
881        } else if (strpos($useragent, 'Outlook') !== false) {
882            return false;
883        } else if (strpos($useragent, 'Meridio') !== false) {
884            return false;
885        }
886        // It's Office, not Outlook and not Meridio - so it's probably Word, but we can't really be sure in most cases.
887        return true;
888    }
889
890    /**
891     * Check if the user agent matches a given brand.
892     *
893     * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX'
894     *
895     * @param string $brand
896     * @return bool
897     */
898    public static function check_browser_operating_system($brand) {
899        $useragent = self::get_user_agent_string();
900        return ($useragent !== false && preg_match("/$brand/i", $useragent));
901    }
902
903    /**
904     * Gets an array of CSS classes to represent the user agent.
905     * @return array
906     */
907    public static function get_browser_version_classes() {
908        $classes = array();
909        if (self::is_ie()) {
910            $classes[] = 'ie';
911            for ($i = 12; $i >= 6; $i--) {
912                if (self::check_ie_version($i)) {
913                    $classes[] = 'ie'.$i;
914                    break;
915                }
916            }
917        } else if (self::is_firefox() || self::is_gecko() || self::check_camino_version()) {
918            $classes[] = 'gecko';
919            if (preg_match('/rv\:([1-2])\.([0-9])/', self::get_user_agent_string(), $matches)) {
920                $classes[] = "gecko{$matches[1]}{$matches[2]}";
921            }
922        } else if (self::is_webkit()) {
923            $classes[] = 'safari';
924            if (self::is_safari_ios()) {
925                $classes[] = 'ios';
926            } else if (self::is_webkit_android()) {
927                $classes[] = 'android';
928            }
929        } else if (self::is_opera()) {
930            $classes[] = 'opera';
931        }
932        return $classes;
933    }
934
935    /**
936     * Returns true if the user agent supports the display of SVG images.
937     *
938     * @return bool
939     */
940    public static function supports_svg() {
941        // IE 5 - 8 don't support SVG at all.
942        $instance = self::instance();
943        if ($instance->supportssvg === null) {
944            if ($instance->useragent === false) {
945                // Can't be sure, just say no.
946                $instance->supportssvg = false;
947            } else if (self::check_ie_version('0') and !self::check_ie_version('9')) {
948                // IE < 9 doesn't support SVG. Say no.
949                $instance->supportssvg = false;
950            } else if (self::is_ie() and !self::check_ie_version('10') and self::check_ie_compatibility_view()) {
951                // IE 9 Compatibility View doesn't support SVG. Say no.
952                $instance->supportssvg = false;
953            } else if (preg_match('#Android +[0-2]\.#', $instance->useragent)) {
954                // Android < 3 doesn't support SVG. Say no.
955                $instance->supportssvg = false;
956            } else if (self::is_opera()) {
957                // Opera 12 still does not support SVG well enough. Say no.
958                $instance->supportssvg = false;
959            } else {
960                // Presumed fine.
961                $instance->supportssvg = true;
962            }
963        }
964        return $instance->supportssvg;
965    }
966
967    /**
968     * Returns true if the user agent supports the MIME media type for JSON text, as defined in RFC 4627.
969     *
970     * @return bool
971     */
972    public static function supports_json_contenttype() {
973        // Modern browsers other than IE correctly supports 'application/json' media type.
974        if (!self::check_ie_version('0')) {
975            return true;
976        }
977
978        // IE8+ supports 'application/json' media type, when NOT in Compatibility View mode.
979        // Refs:
980        // - http://blogs.msdn.com/b/ie/archive/2008/09/10/native-json-in-ie8.aspx;
981        // - MDL-39810: issues when using 'text/plain' in Compatibility View for the body of an HTTP POST response.
982        if (self::check_ie_version(8) && !self::check_ie_compatibility_view()) {
983            return true;
984        }
985
986        // This browser does not support json.
987        return false;
988    }
989
990    /**
991     * Returns true if the client appears to be some kind of web crawler.
992     * This may include other types of crawler.
993     *
994     * @return bool
995     */
996    public static function is_web_crawler() {
997        $instance = self::instance();
998        return (bool) $instance->is_useragent_web_crawler();
999    }
1000}
Note: See TracBrowser for help on using the repository browser.