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