[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/classes/ -> useragent.php (source)

   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       * 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  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1