See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 403]
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 */ 40 class 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 * Get the MoodleBot UserAgent for this site. 142 * 143 * @return string UserAgent 144 */ 145 public static function get_moodlebot_useragent() { 146 global $CFG; 147 148 $version = moodle_major_version(); // Only major version for security. 149 return "MoodleBot/$version (+{$CFG->wwwroot})"; 150 } 151 152 /** 153 * Returns the user agent string. 154 * @return bool|string The user agent string or false if one isn't available. 155 */ 156 public static function get_user_agent_string() { 157 $instance = self::instance(); 158 return $instance->useragent; 159 } 160 161 /** 162 * Returns the device type we believe is being used. 163 * @return string 164 */ 165 public static function get_device_type() { 166 $instance = self::instance(); 167 if ($instance->devicetype === null) { 168 return $instance->guess_device_type(); 169 } 170 return $instance->devicetype; 171 } 172 173 /** 174 * Guesses the device type the user agent is running on. 175 * 176 * @return string 177 */ 178 protected function guess_device_type() { 179 global $CFG; 180 if (empty($CFG->enabledevicedetection)) { 181 $this->devicetype = self::DEVICETYPE_DEFAULT; 182 return $this->devicetype; 183 } 184 foreach ($this->devicetypecustoms as $value => $regex) { 185 if (preg_match($regex, $this->useragent)) { 186 $this->devicetype = $value; 187 return $this->devicetype; 188 } 189 } 190 if ($this->is_useragent_mobile()) { 191 $this->devicetype = self::DEVICETYPE_MOBILE; 192 } else if ($this->is_useragent_tablet()) { 193 $this->devicetype = self::DEVICETYPE_TABLET; 194 } else if (self::check_ie_version('0') && !self::check_ie_version('7.0')) { 195 // IE 6 and before are considered legacy. 196 $this->devicetype = self::DEVICETYPE_LEGACY; 197 } else { 198 $this->devicetype = self::DEVICETYPE_DEFAULT; 199 } 200 return $this->devicetype; 201 } 202 203 /** 204 * Returns true if the user appears to be on a mobile device. 205 * @return bool 206 */ 207 protected function is_useragent_mobile() { 208 // Mobile detection PHP direct copy from open source detectmobilebrowser.com. 209 $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'; 210 $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'; 211 return (preg_match($phonesregex, $this->useragent) || preg_match($modelsregex, substr($this->useragent, 0, 4))); 212 } 213 214 /** 215 * Returns true if the user appears to be on a tablet. 216 * 217 * @return int 218 */ 219 protected function is_useragent_tablet() { 220 $tabletregex = '/Tablet browser|android|iPad|iProd|GT-P1000|GT-I9000|SHW-M180S|SGH-T849|SCH-I800|Build\/ERE27|sholest/i'; 221 return (preg_match($tabletregex, $this->useragent)); 222 } 223 224 /** 225 * Whether the user agent relates to a web crawler. 226 * This includes all types of web crawler. 227 * @return bool 228 */ 229 protected function is_useragent_web_crawler() { 230 $regex = '/MoodleBot|Googlebot|google\.com|Yahoo! Slurp|\[ZSEBOT\]|msnbot|bingbot|BingPreview|Yandex|AltaVista' 231 .'|Baiduspider|Teoma/i'; 232 return (preg_match($regex, $this->useragent)); 233 } 234 235 /** 236 * Gets a list of known device types. 237 * 238 * @param bool $includecustomtypes If set to true we'll include types that have been added by the admin. 239 * @return array 240 */ 241 public static function get_device_type_list($includecustomtypes = true) { 242 $types = self::$devicetypes; 243 if ($includecustomtypes) { 244 $instance = self::instance(); 245 $types = array_merge($types, array_keys($instance->devicetypecustoms)); 246 } 247 return $types; 248 } 249 250 /** 251 * Returns the theme to use for the given device type. 252 * 253 * This used to be get_selected_theme_for_device_type. 254 * @param null|string $devicetype The device type to find out for. Defaults to the device the user is using, 255 * @return bool 256 */ 257 public static function get_device_type_theme($devicetype = null) { 258 global $CFG; 259 if ($devicetype === null) { 260 $devicetype = self::get_device_type(); 261 } 262 $themevarname = self::get_device_type_cfg_var_name($devicetype); 263 if (empty($CFG->$themevarname)) { 264 return false; 265 } 266 return $CFG->$themevarname; 267 } 268 269 /** 270 * Returns the CFG var used to find the theme to use for the given device. 271 * 272 * Used to be get_device_cfg_var_name. 273 * 274 * @param null|string $devicetype The device type to find out for. Defaults to the device the user is using, 275 * @return string 276 */ 277 public static function get_device_type_cfg_var_name($devicetype = null) { 278 if ($devicetype == self::DEVICETYPE_DEFAULT || empty($devicetype)) { 279 return 'theme'; 280 } 281 return 'theme' . $devicetype; 282 } 283 284 /** 285 * Gets the device type the user is currently using. 286 * @return string 287 */ 288 public static function get_user_device_type() { 289 $device = self::get_device_type(); 290 $switched = get_user_preferences('switchdevice'.$device, false); 291 if ($switched != false) { 292 return $switched; 293 } 294 return $device; 295 } 296 297 /** 298 * Switches the device type we think the user is using to what ever was given. 299 * @param string $newdevice 300 * @return bool 301 * @throws coding_exception 302 */ 303 public static function set_user_device_type($newdevice) { 304 $devicetype = self::get_device_type(); 305 if ($newdevice == $devicetype) { 306 unset_user_preference('switchdevice'.$devicetype); 307 return true; 308 } else { 309 $devicetypes = self::get_device_type_list(); 310 if (in_array($newdevice, $devicetypes)) { 311 set_user_preference('switchdevice'.$devicetype, $newdevice); 312 return true; 313 } 314 } 315 throw new coding_exception('Invalid device type provided to set_user_device_type'); 316 } 317 318 /** 319 * Returns true if the user agent matches the given brand and the version is equal to or greater than that specified. 320 * 321 * @param string $brand The branch to check for. 322 * @param scalar $version The version if we need to find out if it is equal to or greater than that specified. 323 * @return bool 324 */ 325 public static function check_browser_version($brand, $version = null) { 326 switch ($brand) { 327 328 case 'MSIE': 329 // Internet Explorer. 330 return self::check_ie_version($version); 331 332 case 'Edge': 333 // Microsoft Edge. 334 return self::check_edge_version($version); 335 336 case 'Firefox': 337 // Mozilla Firefox browsers. 338 return self::check_firefox_version($version); 339 340 case 'Chrome': 341 return self::check_chrome_version($version); 342 343 case 'Opera': 344 // Opera. 345 return self::check_opera_version($version); 346 347 case 'Safari': 348 // Desktop version of Apple Safari browser - no mobile or touch devices. 349 return self::check_safari_version($version); 350 351 case 'Safari iOS': 352 // Safari on iPhone, iPad and iPod touch. 353 return self::check_safari_ios_version($version); 354 355 case 'WebKit': 356 // WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles). 357 return self::check_webkit_version($version); 358 359 case 'Gecko': 360 // Gecko based browsers. 361 return self::check_gecko_version($version); 362 363 case 'WebKit Android': 364 // WebKit browser on Android. 365 return self::check_webkit_android_version($version); 366 367 case 'Camino': 368 // OSX browser using Gecke engine. 369 return self::check_camino_version($version); 370 } 371 // Who knows?! doesn't pass anyway. 372 return false; 373 } 374 375 /** 376 * Checks the user agent is camino based and that the version is equal to or greater than that specified. 377 * 378 * Camino browser is at the end of its life, its no longer being developed or supported, just don't worry about it. 379 * 380 * @param string|int $version A version to check for, returns true if its equal to or greater than that specified. 381 * @return bool 382 */ 383 protected static function check_camino_version($version = null) { 384 // OSX browser using Gecko engine. 385 $useragent = self::get_user_agent_string(); 386 if ($useragent === false) { 387 return false; 388 } 389 if (strpos($useragent, 'Camino') === false) { 390 return false; 391 } 392 if (empty($version)) { 393 return true; // No version specified. 394 } 395 if (preg_match("/Camino\/([0-9\.]+)/i", $useragent, $match)) { 396 if (version_compare($match[1], $version) >= 0) { 397 return true; 398 } 399 } 400 return false; 401 } 402 403 /** 404 * Checks the user agent is Firefox (of any version). 405 * 406 * @return bool true if firefox 407 */ 408 public static function is_firefox() { 409 return self::check_firefox_version(); 410 } 411 412 /** 413 * Checks the user agent is Firefox based and that the version is equal to or greater than that specified. 414 * 415 * @param string|int $version A version to check for, returns true if its equal to or greater than that specified. 416 * @return bool 417 */ 418 public static function check_firefox_version($version = null) { 419 // Mozilla Firefox browsers. 420 $useragent = self::get_user_agent_string(); 421 if ($useragent === false) { 422 return false; 423 } 424 if (strpos($useragent, 'Firefox') === false && strpos($useragent, 'Iceweasel') === false) { 425 return false; 426 } 427 if (empty($version)) { 428 return true; // No version specified.. 429 } 430 if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $useragent, $match)) { 431 if (version_compare($match[2], $version) >= 0) { 432 return true; 433 } 434 } 435 return false; 436 } 437 438 /** 439 * Checks the user agent is Gecko based (of any version). 440 * 441 * @return bool true if Gecko based. 442 */ 443 public static function is_gecko() { 444 return self::check_gecko_version(); 445 } 446 447 /** 448 * Checks the user agent is Gecko based and that the version is equal to or greater than that specified. 449 * 450 * @param string|int $version A version to check for, returns true if its equal to or greater than that specified. 451 * @return bool 452 */ 453 public static function check_gecko_version($version = null) { 454 // Gecko based browsers. 455 // Do not look for dates any more, we expect real Firefox version here. 456 $useragent = self::get_user_agent_string(); 457 if ($useragent === false) { 458 return false; 459 } 460 if (empty($version)) { 461 $version = 1; 462 } else if ($version > 20000000) { 463 // This is just a guess, it is not supposed to be 100% accurate! 464 if (preg_match('/^201/', $version)) { 465 $version = 3.6; 466 } else if (preg_match('/^200[7-9]/', $version)) { 467 $version = 3; 468 } else if (preg_match('/^2006/', $version)) { 469 $version = 2; 470 } else { 471 $version = 1.5; 472 } 473 } 474 if (preg_match("/(Iceweasel|Firefox)\/([0-9\.]+)/i", $useragent, $match)) { 475 // Use real Firefox version if specified in user agent string. 476 if (version_compare($match[2], $version) >= 0) { 477 return true; 478 } 479 } else if (preg_match("/Gecko\/([0-9\.]+)/i", $useragent, $match)) { 480 // Gecko might contain date or Firefox revision, let's just guess the Firefox version from the date. 481 $browserver = $match[1]; 482 if ($browserver > 20000000) { 483 // This is just a guess, it is not supposed to be 100% accurate! 484 if (preg_match('/^201/', $browserver)) { 485 $browserver = 3.6; 486 } else if (preg_match('/^200[7-9]/', $browserver)) { 487 $browserver = 3; 488 } else if (preg_match('/^2006/', $version)) { 489 $browserver = 2; 490 } else { 491 $browserver = 1.5; 492 } 493 } 494 if (version_compare($browserver, $version) >= 0) { 495 return true; 496 } 497 } 498 return false; 499 } 500 501 /** 502 * Checks the user agent is Edge (of any version). 503 * 504 * @return bool true if Edge 505 */ 506 public static function is_edge() { 507 return self::check_edge_version(); 508 } 509 510 /** 511 * Check the User Agent for the version of Edge. 512 * 513 * @param string|int $version A version to check for, returns true if its equal to or greater than that specified. 514 * @return bool 515 */ 516 public static function check_edge_version($version = null) { 517 $useragent = self::get_user_agent_string(); 518 519 if ($useragent === false) { 520 // No User Agent found. 521 return false; 522 } 523 524 if (strpos($useragent, 'Edge/') === false) { 525 // Edge was not found in the UA - this is not Edge. 526 return false; 527 } 528 529 if (empty($version)) { 530 // No version to check. 531 return true; 532 } 533 534 // Find the version. 535 // Edge versions are always in the format: 536 // Edge/<version>.<OS build number> 537 preg_match('%Edge/([\d]+)\.(.*)$%', $useragent, $matches); 538 539 // Just to be safe, round the version being tested. 540 // Edge only uses integer versions - the second component is the OS build number. 541 $version = round($version); 542 543 // Check whether the version specified is >= the version found. 544 return version_compare($matches[1], $version, '>='); 545 } 546 547 /** 548 * Checks the user agent is IE (of any version). 549 * 550 * @return bool true if internet exporeer 551 */ 552 public static function is_ie() { 553 return self::check_ie_version(); 554 } 555 556 /** 557 * Checks the user agent is IE and returns its main properties: 558 * - browser version; 559 * - whether running in compatibility view. 560 * 561 * @return bool|array False if not IE, otherwise an associative array of properties. 562 */ 563 public static function check_ie_properties() { 564 // Internet Explorer. 565 $useragent = self::get_user_agent_string(); 566 if ($useragent === false) { 567 return false; 568 } 569 if (strpos($useragent, 'Opera') !== false) { 570 // Reject Opera. 571 return false; 572 } 573 // See: http://www.useragentstring.com/pages/Internet%20Explorer/. 574 if (preg_match("/MSIE ([0-9\.]+)/", $useragent, $match)) { 575 $browser = $match[1]; 576 // See: http://msdn.microsoft.com/en-us/library/ie/bg182625%28v=vs.85%29.aspx for IE11+ useragent details. 577 } else if (preg_match("/Trident\/[0-9\.]+/", $useragent) && preg_match("/rv:([0-9\.]+)/", $useragent, $match)) { 578 $browser = $match[1]; 579 } else { 580 return false; 581 } 582 583 $compatview = false; 584 // IE8 and later versions may pretend to be IE7 for intranet sites, use Trident version instead, 585 // the Trident should always describe the capabilities of IE in any emulation mode. 586 if ($browser === '7.0' and preg_match("/Trident\/([0-9\.]+)/", $useragent, $match)) { 587 $compatview = true; 588 $browser = $match[1] + 4; // NOTE: Hopefully this will work also for future IE versions. 589 } 590 $browser = round($browser, 1); 591 return array( 592 'version' => $browser, 593 'compatview' => $compatview 594 ); 595 } 596 597 /** 598 * Checks the user agent is IE and that the version is equal to or greater than that specified. 599 * 600 * @param string|int $version A version to check for, returns true if its equal to or greater than that specified. 601 * @return bool 602 */ 603 public static function check_ie_version($version = null) { 604 // Internet Explorer. 605 $properties = self::check_ie_properties(); 606 if (!is_array($properties)) { 607 return false; 608 } 609 // In case of IE we have to deal with BC of the version parameter. 610 if (is_null($version)) { 611 $version = 5.5; // Anything older is not considered a browser at all! 612 } 613 // IE uses simple versions, let's cast it to float to simplify the logic here. 614 $version = round($version, 1); 615 return ($properties['version'] >= $version); 616 } 617 618 /** 619 * Checks the user agent is IE and that IE is running under Compatibility View setting. 620 * 621 * @return bool true if internet explorer runs in Compatibility View mode. 622 */ 623 public static function check_ie_compatibility_view() { 624 // IE User Agent string when in Compatibility View: 625 // - IE 8: "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/4.0; ...)". 626 // - IE 9: "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/5.0; ...)". 627 // - IE 10: "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/6.0; ...)". 628 // - IE 11: "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0; ...)". 629 // Refs: 630 // - http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx. 631 // - http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx. 632 // - http://blogs.msdn.com/b/ie/archive/2011/04/15/the-ie10-user-agent-string.aspx. 633 // - http://msdn.microsoft.com/en-us/library/ie/hh869301%28v=vs.85%29.aspx. 634 $properties = self::check_ie_properties(); 635 if (!is_array($properties)) { 636 return false; 637 } 638 return $properties['compatview']; 639 } 640 641 /** 642 * Checks the user agent is Opera (of any version). 643 * 644 * @return bool true if opera 645 */ 646 public static function is_opera() { 647 return self::check_opera_version(); 648 } 649 650 /** 651 * Checks the user agent is Opera and that the version is equal to or greater than that specified. 652 * 653 * @param string|int $version A version to check for, returns true if its equal to or greater than that specified. 654 * @return bool 655 */ 656 public static function check_opera_version($version = null) { 657 // Opera. 658 $useragent = self::get_user_agent_string(); 659 if ($useragent === false) { 660 return false; 661 } 662 if (strpos($useragent, 'Opera') === false) { 663 return false; 664 } 665 if (empty($version)) { 666 return true; // No version specified. 667 } 668 // Recent Opera useragents have Version/ with the actual version, e.g.: 669 // Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.289 Version/12.01 670 // That's Opera 12.01, not 9.8. 671 if (preg_match("/Version\/([0-9\.]+)/i", $useragent, $match)) { 672 if (version_compare($match[1], $version) >= 0) { 673 return true; 674 } 675 } else if (preg_match("/Opera\/([0-9\.]+)/i", $useragent, $match)) { 676 if (version_compare($match[1], $version) >= 0) { 677 return true; 678 } 679 } 680 return false; 681 } 682 683 /** 684 * Checks the user agent is webkit based 685 * 686 * @return bool true if webkit 687 */ 688 public static function is_webkit() { 689 return self::check_webkit_version(); 690 } 691 692 /** 693 * Checks the user agent is Webkit based and that the version is equal to or greater than that specified. 694 * 695 * @param string|int $version A version to check for, returns true if its equal to or greater than that specified. 696 * @return bool 697 */ 698 public static function check_webkit_version($version = null) { 699 // WebKit based browser - everything derived from it (Safari, Chrome, iOS, Android and other mobiles). 700 $useragent = self::get_user_agent_string(); 701 if ($useragent === false) { 702 return false; 703 } 704 if (strpos($useragent, 'AppleWebKit') === false) { 705 return false; 706 } 707 if (empty($version)) { 708 return true; // No version specified. 709 } 710 if (preg_match("/AppleWebKit\/([0-9.]+)/i", $useragent, $match)) { 711 if (version_compare($match[1], $version) >= 0) { 712 return true; 713 } 714 } 715 return false; 716 } 717 718 /** 719 * Checks the user agent is Safari 720 * 721 * @return bool true if safari 722 */ 723 public static function is_safari() { 724 return self::check_safari_version(); 725 } 726 727 /** 728 * Checks the user agent is Safari based and that the version is equal to or greater than that specified. 729 * 730 * @param string|int $version A version to check for, returns true if its equal to or greater than that specified. 731 * @return bool 732 */ 733 public static function check_safari_version($version = null) { 734 // Desktop version of Apple Safari browser - no mobile or touch devices. 735 $useragent = self::get_user_agent_string(); 736 if ($useragent === false) { 737 return false; 738 } 739 if (strpos($useragent, 'AppleWebKit') === false) { 740 return false; 741 } 742 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices. 743 if (strpos($useragent, 'OmniWeb')) { 744 // Reject OmniWeb. 745 return false; 746 } 747 if (strpos($useragent, 'Shiira')) { 748 // Reject Shiira. 749 return false; 750 } 751 if (strpos($useragent, 'SymbianOS')) { 752 // Reject SymbianOS. 753 return false; 754 } 755 if (strpos($useragent, 'Android')) { 756 // Reject Androids too. 757 return false; 758 } 759 if (strpos($useragent, 'iPhone') or strpos($useragent, 'iPad') or strpos($useragent, 'iPod')) { 760 // No Apple mobile devices here - editor does not work, course ajax is not touch compatible, etc. 761 return false; 762 } 763 if (strpos($useragent, 'Chrome')) { 764 // Reject chrome browsers - it needs to be tested explicitly. 765 // This will also reject Edge, which pretends to be both Chrome, and Safari. 766 return false; 767 } 768 769 if (empty($version)) { 770 return true; // No version specified. 771 } 772 if (preg_match("/AppleWebKit\/([0-9.]+)/i", $useragent, $match)) { 773 if (version_compare($match[1], $version) >= 0) { 774 return true; 775 } 776 } 777 return false; 778 } 779 780 /** 781 * Checks the user agent is Chrome 782 * 783 * @return bool true if chrome 784 */ 785 public static function is_chrome() { 786 return self::check_chrome_version(); 787 } 788 789 /** 790 * Checks the user agent is Chrome based and that the version is equal to or greater than that specified. 791 * 792 * @param string|int $version A version to check for, returns true if its equal to or greater than that specified. 793 * @return bool 794 */ 795 public static function check_chrome_version($version = null) { 796 // Chrome. 797 $useragent = self::get_user_agent_string(); 798 if ($useragent === false) { 799 return false; 800 } 801 if (strpos($useragent, 'Chrome') === false) { 802 return false; 803 } 804 if (empty($version)) { 805 return true; // No version specified. 806 } 807 if (preg_match("/Chrome\/(.*)[ ]+/i", $useragent, $match)) { 808 if (version_compare($match[1], $version) >= 0) { 809 return true; 810 } 811 } 812 return false; 813 } 814 815 /** 816 * Checks the user agent is webkit android based. 817 * 818 * @return bool true if webkit based and on Android 819 */ 820 public static function is_webkit_android() { 821 return self::check_webkit_android_version(); 822 } 823 824 /** 825 * Checks the user agent is Webkit based and on Android and that the version is equal to or greater than that specified. 826 * 827 * @param string|int $version A version to check for, returns true if its equal to or greater than that specified. 828 * @return bool 829 */ 830 public static function check_webkit_android_version($version = null) { 831 // WebKit browser on Android. 832 $useragent = self::get_user_agent_string(); 833 if ($useragent === false) { 834 return false; 835 } 836 if (strpos($useragent, 'Android') === false) { 837 return false; 838 } 839 if (empty($version)) { 840 return true; // No version specified. 841 } 842 if (preg_match("/AppleWebKit\/([0-9]+)/i", $useragent, $match)) { 843 if (version_compare($match[1], $version) >= 0) { 844 return true; 845 } 846 } 847 return false; 848 } 849 850 /** 851 * Checks the user agent is Safari on iOS 852 * 853 * @return bool true if Safari on iOS 854 */ 855 public static function is_safari_ios() { 856 return self::check_safari_ios_version(); 857 } 858 859 /** 860 * Checks the user agent is Safari on iOS and that the version is equal to or greater than that specified. 861 * 862 * @param string|int $version A version to check for, returns true if its equal to or greater than that specified. 863 * @return bool 864 */ 865 public static function check_safari_ios_version($version = null) { 866 // Safari on iPhone, iPad and iPod touch. 867 $useragent = self::get_user_agent_string(); 868 if ($useragent === false) { 869 return false; 870 } 871 if (strpos($useragent, 'AppleWebKit') === false or strpos($useragent, 'Safari') === false) { 872 return false; 873 } 874 if (!strpos($useragent, 'iPhone') and !strpos($useragent, 'iPad') and !strpos($useragent, 'iPod')) { 875 return false; 876 } 877 if (empty($version)) { 878 return true; // No version specified. 879 } 880 if (preg_match("/AppleWebKit\/([0-9]+)/i", $useragent, $match)) { 881 if (version_compare($match[1], $version) >= 0) { 882 return true; 883 } 884 } 885 return false; 886 } 887 888 /** 889 * Checks if the user agent is MS Word. 890 * Not perfect, as older versions of Word use standard IE6/7 user agents without any identifying traits. 891 * 892 * @return bool true if user agent could be identified as MS Word. 893 */ 894 public static function is_msword() { 895 $useragent = self::get_user_agent_string(); 896 if (!preg_match('/(\bWord\b|ms-office|MSOffice|Microsoft Office)/i', $useragent)) { 897 return false; 898 } else if (strpos($useragent, 'Outlook') !== false) { 899 return false; 900 } else if (strpos($useragent, 'Meridio') !== false) { 901 return false; 902 } 903 // It's Office, not Outlook and not Meridio - so it's probably Word, but we can't really be sure in most cases. 904 return true; 905 } 906 907 /** 908 * Check if the user agent matches a given brand. 909 * 910 * Known brand: 'Windows','Linux','Macintosh','SGI','SunOS','HP-UX' 911 * 912 * @param string $brand 913 * @return bool 914 */ 915 public static function check_browser_operating_system($brand) { 916 $useragent = self::get_user_agent_string(); 917 return ($useragent !== false && preg_match("/$brand/i", $useragent)); 918 } 919 920 /** 921 * Gets an array of CSS classes to represent the user agent. 922 * @return array 923 */ 924 public static function get_browser_version_classes() { 925 $classes = array(); 926 if (self::is_edge()) { 927 $classes[] = 'edge'; 928 } else if (self::is_ie()) { 929 $classes[] = 'ie'; 930 for ($i = 12; $i >= 6; $i--) { 931 if (self::check_ie_version($i)) { 932 $classes[] = 'ie'.$i; 933 break; 934 } 935 } 936 } else if (self::is_firefox() || self::is_gecko() || self::check_camino_version()) { 937 $classes[] = 'gecko'; 938 if (preg_match('/rv\:([1-2])\.([0-9])/', self::get_user_agent_string(), $matches)) { 939 $classes[] = "gecko{$matches[1]}{$matches[2]}"; 940 } 941 } else if (self::is_chrome()) { 942 $classes[] = 'chrome'; 943 if (self::is_webkit_android()) { 944 $classes[] = 'android'; 945 } 946 } else if (self::is_webkit()) { 947 if (self::is_safari()) { 948 $classes[] = 'safari'; 949 } 950 if (self::is_safari_ios()) { 951 $classes[] = 'ios'; 952 } else if (self::is_webkit_android()) { 953 $classes[] = 'android'; // Old pre-Chrome android browsers. 954 } 955 } else if (self::is_opera()) { 956 $classes[] = 'opera'; 957 } 958 return $classes; 959 } 960 961 /** 962 * Returns true if the user agent supports the display of SVG images. 963 * 964 * @return bool 965 */ 966 public static function supports_svg() { 967 // IE 5 - 8 don't support SVG at all. 968 $instance = self::instance(); 969 if ($instance->supportssvg === null) { 970 if ($instance->useragent === false) { 971 // Can't be sure, just say no. 972 $instance->supportssvg = false; 973 } else if (self::check_ie_version('0') and !self::check_ie_version('9')) { 974 // IE < 9 doesn't support SVG. Say no. 975 $instance->supportssvg = false; 976 } else if (self::is_ie() and !self::check_ie_version('10') and self::check_ie_compatibility_view()) { 977 // IE 9 Compatibility View doesn't support SVG. Say no. 978 $instance->supportssvg = false; 979 } else if (preg_match('#Android +[0-2]\.#', $instance->useragent)) { 980 // Android < 3 doesn't support SVG. Say no. 981 $instance->supportssvg = false; 982 } else if (self::is_opera()) { 983 // Opera 12 still does not support SVG well enough. Say no. 984 $instance->supportssvg = false; 985 } else { 986 // Presumed fine. 987 $instance->supportssvg = true; 988 } 989 } 990 return $instance->supportssvg; 991 } 992 993 /** 994 * Returns true if the user agent supports the MIME media type for JSON text, as defined in RFC 4627. 995 * 996 * @return bool 997 */ 998 public static function supports_json_contenttype() { 999 // Modern browsers other than IE correctly supports 'application/json' media type. 1000 if (!self::check_ie_version('0')) { 1001 return true; 1002 } 1003 1004 // IE8+ supports 'application/json' media type, when NOT in Compatibility View mode. 1005 // Refs: 1006 // - http://blogs.msdn.com/b/ie/archive/2008/09/10/native-json-in-ie8.aspx; 1007 // - MDL-39810: issues when using 'text/plain' in Compatibility View for the body of an HTTP POST response. 1008 if (self::check_ie_version(8) && !self::check_ie_compatibility_view()) { 1009 return true; 1010 } 1011 1012 // This browser does not support json. 1013 return false; 1014 } 1015 1016 /** 1017 * Returns true if the client appears to be some kind of web crawler. 1018 * This may include other types of crawler. 1019 * 1020 * @return bool 1021 */ 1022 public static function is_web_crawler() { 1023 $instance = self::instance(); 1024 return (bool) $instance->is_useragent_web_crawler(); 1025 } 1026 1027 /** 1028 * Returns true if the client appears to be a device using iOS (iPhone, iPad, iPod). 1029 * 1030 * @param scalar $version The version if we need to find out if it is equal to or greater than that specified. 1031 * @return bool true if the client is using iOS 1032 * @since Moodle 3.2 1033 */ 1034 public static function is_ios($version = null) { 1035 $useragent = self::get_user_agent_string(); 1036 if ($useragent === false) { 1037 return false; 1038 } 1039 if (strpos($useragent, 'AppleWebKit') === false) { 1040 return false; 1041 } 1042 if (strpos($useragent, 'Windows')) { 1043 // Reject Windows Safari. 1044 return false; 1045 } 1046 if (strpos($useragent, 'Macintosh')) { 1047 // Reject MacOS Safari. 1048 return false; 1049 } 1050 // Look for AppleWebKit, excluding strings with OmniWeb, Shiira and SymbianOS and any other mobile devices. 1051 if (strpos($useragent, 'OmniWeb')) { 1052 // Reject OmniWeb. 1053 return false; 1054 } 1055 if (strpos($useragent, 'Shiira')) { 1056 // Reject Shiira. 1057 return false; 1058 } 1059 if (strpos($useragent, 'SymbianOS')) { 1060 // Reject SymbianOS. 1061 return false; 1062 } 1063 if (strpos($useragent, 'Android')) { 1064 // Reject Androids too. 1065 return false; 1066 } 1067 if (strpos($useragent, 'Chrome')) { 1068 // Reject chrome browsers - it needs to be tested explicitly. 1069 // This will also reject Edge, which pretends to be both Chrome, and Safari. 1070 return false; 1071 } 1072 1073 if (empty($version)) { 1074 return true; // No version specified. 1075 } 1076 if (preg_match("/AppleWebKit\/([0-9.]+)/i", $useragent, $match)) { 1077 if (version_compare($match[1], $version) >= 0) { 1078 return true; 1079 } 1080 } 1081 return false; 1082 } 1083 1084 /** 1085 * Returns true if the client appears to be the Moodle app (or an app based on the Moodle app code). 1086 * 1087 * @return bool true if the client is the Moodle app 1088 * @since Moodle 3.7 1089 */ 1090 public static function is_moodle_app() { 1091 $useragent = self::get_user_agent_string(); 1092 1093 // Make it case insensitive, things can change in the app or desktop app depending on the platform frameworks. 1094 if (stripos($useragent, 'MoodleMobile') !== false) { 1095 return true; 1096 } 1097 1098 return false; 1099 } 1100 1101 /** 1102 * Checks if current browser supports files with give extension as <video> or <audio> source 1103 * 1104 * Note, the check here is not 100% accurate! 1105 * 1106 * First, we do not know which codec is used in .mp4 or .webm files. Not all browsers support 1107 * all codecs. 1108 * 1109 * Also we assume that users of Firefox/Chrome/Safari do not use the ancient versions of browsers. 1110 * 1111 * We check the exact version for IE/Edge though. We know that there are still users of very old 1112 * versions that are afraid to upgrade or have slow IT department. 1113 * 1114 * Resources: 1115 * https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats 1116 * https://en.wikipedia.org/wiki/HTML5_video 1117 * https://en.wikipedia.org/wiki/HTML5_Audio 1118 * 1119 * @param string $extension extension without leading . 1120 * @return bool 1121 */ 1122 public static function supports_html5($extension) { 1123 $extension = strtolower($extension); 1124 1125 $supportedvideo = array('m4v', 'webm', 'ogv', 'mp4', 'mov', 'fmp4'); 1126 $supportedaudio = array('ogg', 'oga', 'aac', 'm4a', 'mp3', 'wav', 'flac'); 1127 1128 // Basic extension support. 1129 if (!in_array($extension, $supportedvideo) && !in_array($extension, $supportedaudio)) { 1130 return false; 1131 } 1132 1133 // MS IE support - version 9.0 or later. 1134 if (self::is_ie() && !self::check_ie_version('9.0')) { 1135 return false; 1136 } 1137 1138 // MS Edge support - version 12.0 for desktop and 13.0 for mobile. 1139 if (self::is_edge()) { 1140 if (!self::check_edge_version('12.0')) { 1141 return false; 1142 } 1143 if (self::instance()->is_useragent_mobile() && !self::check_edge_version('13.0')) { 1144 return false; 1145 } 1146 } 1147 1148 // Different exceptions. 1149 1150 // Webm is not supported in IE, Edge and in Safari. 1151 if ($extension === 'webm' && 1152 (self::is_ie() || self::is_edge() || self::is_safari() || self::is_safari_ios())) { 1153 return false; 1154 } 1155 // Ogg is not supported in IE, Edge and Safari. 1156 $isogg = in_array($extension, ['ogg', 'oga', 'ogv']); 1157 if ($isogg && (self::is_ie() || self::is_edge() || self::is_safari() || self::is_safari_ios())) { 1158 return false; 1159 } 1160 // FLAC is not supported in IE and Edge (below 16.0). 1161 if ($extension === 'flac' && 1162 (self::is_ie() || (self::is_edge() && !self::check_edge_version('16.0')))) { 1163 return false; 1164 } 1165 // Wave is not supported in IE. 1166 if ($extension === 'wav' && self::is_ie()) { 1167 return false; 1168 } 1169 // Aac is not supported in IE below 11.0. 1170 if ($extension === 'aac' && (self::is_ie() && !self::check_ie_version('11.0'))) { 1171 return false; 1172 } 1173 // Mpeg is not supported in IE below 10.0. 1174 $ismpeg = in_array($extension, ['m4a', 'mp3', 'm4v', 'mp4', 'fmp4']); 1175 if ($ismpeg && (self::is_ie() && !self::check_ie_version('10.0'))) { 1176 return false; 1177 } 1178 // Mov is not supported in IE. 1179 if ($extension === 'mov' && self::is_ie()) { 1180 return false; 1181 } 1182 1183 return true; 1184 } 1185 1186 /** 1187 * Checks if current browser supports the HLS and MPEG-DASH media 1188 * streaming formats. Most browsers get this from Media Source Extensions. 1189 * Safari on iOS, doesn't support MPEG-DASH at all. 1190 * 1191 * Note, the check here is not 100% accurate! 1192 * 1193 * Also we assume that users of Firefox/Chrome/Safari do not use the ancient versions of browsers. 1194 * We check the exact version for IE/Edge though. We know that there are still users of very old 1195 * versions that are afraid to upgrade or have slow IT department. 1196 * 1197 * Resources: 1198 * https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_Extensions_API 1199 * https://caniuse.com/#search=mpeg-dash 1200 * https://caniuse.com/#search=hls 1201 * 1202 * @param string $extension 1203 * @return bool 1204 */ 1205 public static function supports_media_source_extensions(string $extension) : bool { 1206 // Not supported in IE below 11.0. 1207 if (self::is_ie() && !self::check_ie_version('11.0')) { 1208 return false; 1209 } 1210 1211 if ($extension == '.mpd') { 1212 // Not supported in Safari on iOS. 1213 if (self::is_safari_ios()) { 1214 return false; 1215 } 1216 } 1217 1218 return true; 1219 } 1220 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body