<?php
/**
* Mobile Detect Library
* =====================
*
* Motto: "Every business should have a mobile detection script to detect mobile readers"
*
* Mobile_Detect is a lightweight PHP class for detecting mobile devices (including tablets).
* It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.
*
* @author Current authors: Serban Ghita <serbanghita@gmail.com>, Nick Ilyin <nick.ilyin@gmail.com>
* Original author: Victor Stanciu <vic.stanciu@gmail.com>
*
* @license Code and contributions have 'MIT License'
* More details: https://github.com/serbanghita/Mobile-Detect/blob/master/LICENSE.txt
*
* @link Homepage: http://mobiledetect.net
* GitHub Repo: https://github.com/serbanghita/Mobile-Detect
* Google Code: http://code.google.com/p/php-mobile-detect/
* README: https://github.com/serbanghita/Mobile-Detect/blob/master/README.md
* HOWTO: https://github.com/serbanghita/Mobile-Detect/wiki/Code-examples
*
* @version 2.8.5
*/
class Mobile_Detect
{
/**
* Mobile detection type.
*
* @deprecated since version 2.6.9
*/
const DETECTION_TYPE_MOBILE = 'mobile';
/**
* Extended detection type.
*
* @deprecated since version 2.6.9
*/
const DETECTION_TYPE_EXTENDED = 'extended';
/**
* A frequently used regular expression to extract version #s.
*
* @deprecated since version 2.6.9
*/
const VER = '([\w._\+]+)';
/**
* Top-level device.
*/
const MOBILE_GRADE_A = 'A';
/**
* Mid-level device.
*/
const MOBILE_GRADE_B = 'B';
/**
* Low-level device.
*/
const MOBILE_GRADE_C = 'C';
/**
* Stores the version number of the current release.
*/
const VERSION = '2.8.5';
/**
* A type for the version() method indicating a string return value.
*/
const VERSION_TYPE_STRING = 'text';
/**
* A type for the version() method indicating a float return value.
*/
const VERSION_TYPE_FLOAT = 'float';
/**
* The User-Agent HTTP header is stored in here.
* @var string
*/
protected $userAgent = null;
/**
* HTTP headers in the PHP-flavor. So HTTP_USER_AGENT and SERVER_SOFTWARE.
* @var array
*/
protected $httpHeaders = array();
/**
* The detection type, using self::DETECTION_TYPE_MOBILE or self::DETECTION_TYPE_EXTENDED.
*
* @deprecated since version 2.6.9
*
* @var string
*/
protected $detectionType = self::DETECTION_TYPE_MOBILE;
/**
* HTTP headers that trigger the 'isMobile' detection
* to be true.
*
* @var array
*/
protected static $mobileHeaders = array(
'HTTP_ACCEPT' => array('matches' => array(
// Opera Mini; @reference: http://dev.opera.com/articles/view/opera-binary-markup-language/
'application/x-obml2d',
// BlackBerry devices.
'application/vnd.rim.html',
'text/vnd.wap.wml',
'application/vnd.wap.xhtml+xml'
)),
'HTTP_X_WAP_PROFILE' => null,
);
/**
* List of mobile devices (phones).
*
* @var array
*/
protected static $phoneDevices = array(
'iPhone' => '\biPhone\b|\biPod\b', // |\biTunes
);
/**
* List of tablet devices.
*
* @var array
*/
protected static $tabletDevices = array(
'iPad' => 'iPad|iPad.*Mobile'
);
/**
* List of mobile Operating Systems.
*
* @var array
*/
protected static $operatingSystems = array(
'AndroidOS' => 'Android',
);
/**
* List of mobile User Agents.
*
* @var array
*/
protected static $browsers = array(
// @reference: https://developers.google.com/chrome/mobile/docs/user-agent
'Chrome' => '\bCrMo\b|CriOS|Android.*Chrome/[.0-9]* (Mobile)?',
);
/**
* Utilities.
*
* @var array
*/
protected static $utilities = array(
// Experimental. When a mobile device wants to switch to 'Desktop Mode'.
// http://scottcate.com/technology/windows-phone-8-ie10-desktop-or-mobile/
// https://github.com/serbanghita/Mobile-Detect/issues/57#issuecomment-15024011
'DesktopMode' => 'WPDesktop',
'TV' => 'SonyDTV|HbbTV', // experimental
'WebKit' => '(webkit)[ /]([\w.]+)',
'Bot' => 'Googlebot|YandexBot|bingbot|ia_archiver|AhrefsBot|Ezooms|GSLFbot|WBSearchBot|Twitterbot|TweetmemeBot|Twikle|PaperLiBot|Wotbox|UnwindFetchor|facebookexternalhit',
'MobileBot' => 'Googlebot-Mobile|YahooSeeker/M1A1-R2D2',
// @todo: Include JXD consoles.
'Console' => '\b(Nintendo|Nintendo WiiU|Nintendo 3DS|PLAYSTATION|Xbox)\b',
'Watch' => 'SM-V700',
);
/**
* All possible HTTP headers that represent the
* User-Agent string.
*
* @var array
*/
protected static $uaHttpHeaders = array(
// The default User-Agent string.
'HTTP_USER_AGENT',
// Header can occur on devices using Opera Mini.
'HTTP_X_OPERAMINI_PHONE_UA',
// Vodafone specific header: http://www.seoprinciple.com/mobile-web-community-still-angry-at-vodafone/24/
'HTTP_X_DEVICE_USER_AGENT',
'HTTP_X_ORIGINAL_USER_AGENT',
'HTTP_X_SKYFIRE_PHONE',
'HTTP_X_BOLT_PHONE_UA',
'HTTP_DEVICE_STOCK_UA',
'HTTP_X_UCBROWSER_DEVICE_UA'
);
/**
* The individual segments that could exist in a User-Agent string. VER refers to the regular
* expression defined in the constant self::VER.
*
* @var array
*/
protected static $properties = array(
'webOS' => array('webOS/[VER]', 'hpwOS/[VER];'),
);
/**
* Construct an instance of this class.
*
* @param array $headers Specify the headers as injection. Should be PHP _SERVER flavored.
* If left empty, will use the global _SERVER['HTTP_*'] vars instead.
* @param string $userAgent Inject the User-Agent header. If null, will use HTTP_USER_AGENT
* from the $headers array instead.
*/
public function __construct(
array $headers = null,
$userAgent = null
) {
$this->setHttpHeaders($headers);
$this->setUserAgent($userAgent);
}
/**
* Get the current script version.
* This is useful for the demo.php file,
* so people can check on what version they are testing
* for mobile devices.
*
* @return string The version number in semantic version format.
*/
public static function getScriptVersion()
{
return self::VERSION;
}
/**
* Set the HTTP Headers. Must be PHP-flavored. This method will reset existing headers.
*
* @param array $httpHeaders The headers to set. If null, then using PHP's _SERVER to extract
* the headers. The default null is left for backwards compatibilty.
*/
public function setHttpHeaders($httpHeaders = null)
{
//use global _SERVER if $httpHeaders aren't defined
if (!is_array($httpHeaders) || !count($httpHeaders)) {
$httpHeaders = $_SERVER;
}
//clear existing headers
$this->httpHeaders = array();
//Only save HTTP headers. In PHP land, that means only _SERVER vars that
//start with HTTP_.
foreach ($httpHeaders as $key => $value) {
if (substr($key,0,5) == 'HTTP_') {
$this->httpHeaders[$key] = $value;
}
}
}
/**
* Retrieves the HTTP headers.
*
* @return array
*/
public function getHttpHeaders()
{
return $this->httpHeaders;
}
/**
* Retrieves a particular header. If it doesn't exist, no exception/error is caused.
* Simply null is returned.
*
* @param string $header The name of the header to retrieve. Can be HTTP compliant such as
* "User-Agent" or "X-Device-User-Agent" or can be php-esque with the
* all-caps, HTTP_ prefixed, underscore seperated awesomeness.
*
* @return string|null The value of the header.
*/
public function getHttpHeader($header)
{
//are we using PHP-flavored headers?
if (strpos($header, '_') === false) {
$header = str_replace('-', '_', $header);
$header = strtoupper($header);
}
//test the alternate, too
$altHeader = 'HTTP_' . $header;
//Test both the regular and the HTTP_ prefix
if (isset($this->httpHeaders[$header])) {
return $this->httpHeaders[$header];
} elseif (isset($this->httpHeaders[$altHeader])) {
return $this->httpHeaders[$altHeader];
}
return null;
}
public function getMobileHeaders()
{
return self::$mobileHeaders;
}
/**
* Get all possible HTTP headers that
* can contain the User-Agent string.
*
* @return array List of HTTP headers.
*/
public function getUaHttpHeaders()
{
return self::$uaHttpHeaders;
}
/**
* Set the User-Agent to be used.
*
* @param string $userAgent The user agent string to set.
*
* @return string|null
*/
public function setUserAgent($userAgent = null)
{
if (!empty($userAgent)) {
return $this->userAgent = $userAgent;
} else {
$this->userAgent = null;
foreach ($this->getUaHttpHeaders() as $altHeader) {
if (!empty($this->httpHeaders[$altHeader])) { // @todo: should use getHttpHeader(), but it would be slow. (Serban)
$this->userAgent .= $this->httpHeaders[$altHeader] . " ";
}
}
return $this->userAgent = (!empty($this->userAgent) ? trim($this->userAgent) : null);
}
}
/**
* Retrieve the User-Agent.
*
* @return string|null The user agent if it's set.
*/
public function getUserAgent()
{
return $this->userAgent;
}
/**
* Set the detection type. Must be one of self::DETECTION_TYPE_MOBILE or
* self::DETECTION_TYPE_EXTENDED. Otherwise, nothing is set.
*
* @deprecated since version 2.6.9
*
* @param string $type The type. Must be a self::DETECTION_TYPE_* constant. The default
* parameter is null which will default to self::DETECTION_TYPE_MOBILE.
*/
public function setDetectionType($type = null)
{
if ($type === null) {
$type = self::DETECTION_TYPE_MOBILE;
}
if ($type != self::DETECTION_TYPE_MOBILE && $type != self::DETECTION_TYPE_EXTENDED) {
return;
}
$this->detectionType = $type;
}
/**
* Retrieve the list of known phone devices.
*
* @return array List of phone devices.
*/
public static function getPhoneDevices()
{
return self::$phoneDevices;
}
/**
* Retrieve the list of known tablet devices.
*
* @return array List of tablet devices.
*/
public static function getTabletDevices()
{
return self::$tabletDevices;
}
/**
* Alias for getBrowsers() method.
*
* @return array List of user agents.
*/
public static function getUserAgents()
{
return self::getBrowsers();
}
/**
* Retrieve the list of known browsers. Specifically, the user agents.
*
* @return array List of browsers / user agents.
*/
public static function getBrowsers()
{
return self::$browsers;
}
/**
* Retrieve the list of known utilities.
*
* @return array List of utilities.
*/
public static function getUtilities()
{
return self::$utilities;
}
/**
* Method gets the mobile detection rules. This method is used for the magic methods $detect->is*().
*
* @deprecated since version 2.6.9
*
* @return array All the rules (but not extended).
*/
public static function getMobileDetectionRules()
{
static $rules;
if (!$rules) {
$rules = array_merge(
self::$phoneDevices,
self::$tabletDevices,
self::$operatingSystems,
self::$browsers
);
}
return $rules;
}
/**
* Method gets the mobile detection rules + utilities.
* The reason this is separate is because utilities rules
* don't necessary imply mobile. This method is used inside
* the new $detect->is('stuff') method.
*
* @deprecated since version 2.6.9
*
* @return array All the rules + extended.
*/
public function getMobileDetectionRulesExtended()
{
static $rules;
if (!$rules) {
// Merge all rules together.
$rules = array_merge(
self::$phoneDevices,
self::$tabletDevices,
self::$operatingSystems,
self::$browsers,
self::$utilities
);
}
return $rules;
}
/**
* Retrieve the current set of rules.
*
* @deprecated since version 2.6.9
*
* @return array
*/
public function getRules()
{
if ($this->detectionType == self::DETECTION_TYPE_EXTENDED) {
return self::getMobileDetectionRulesExtended();
} else {
return self::getMobileDetectionRules();
}
}
/**
* Retrieve the list of mobile operating systems.
*
* @return array The list of mobile operating systems.
*/
public static function getOperatingSystems()
{
return self::$operatingSystems;
}
/**
* Check the HTTP headers for signs of mobile.
* This is the fastest mobile check possible; it's used
* inside isMobile() method.
*
* @return bool
*/
public function checkHttpHeadersForMobile()
{
foreach ($this->getMobileHeaders() as $mobileHeader => $matchType) {
if ( isset($this->httpHeaders[$mobileHeader]) ) {
if ( is_array($matchType['matches']) ) {
foreach ($matchType['matches'] as $_match) {
if ( strpos($this->httpHeaders[$mobileHeader], $_match) !== false ) {
return true;
}
}
return false;
} else {
return true;
}
}
}
return false;
}
/**
* Magic overloading method.
*
* @method boolean is[...]()
* @param string $name
* @param array $arguments
* @return mixed
* @throws BadMethodCallException when the method doesn't exist and doesn't start with 'is'
*/
public function __call($name, $arguments)
{
//make sure the name starts with 'is', otherwise
if (substr($name, 0, 2) != 'is') {
throw new BadMethodCallException("No such method exists: $name");
}
$this->setDetectionType(self::DETECTION_TYPE_MOBILE);
$key = substr($name, 2);
return $this->matchUAAgainstKey($key);
}
/**
* Find a detection rule that matches the current User-agent.
*
* @param null $userAgent deprecated
* @return boolean
*/
protected function matchDetectionRulesAgainstUA($userAgent = null)
{
// Begin general search.
foreach ($this->getRules() as $_regex) {
if (empty($_regex)) {
continue;
}
if ($this->match($_regex, $userAgent)) {
return true;
}
}
return false;
}
/**
* Search for a certain key in the rules array.
* If the key is found the try to match the corresponding
* regex agains the User-Agent.
*
* @param string $key
* @param null $userAgent deprecated
* @return mixed
*/
protected function matchUAAgainstKey($key, $userAgent = null)
{
// Make the keys lowercase so we can match: isIphone(), isiPhone(), isiphone(), etc.
$key = strtolower($key);
//change the keys to lower case
$_rules = array_change_key_case($this->getRules());
if (array_key_exists($key, $_rules)) {
if (empty($_rules[$key])) {
return null;
}
return $this->match($_rules[$key], $userAgent);
}
return false;
}
/**
* Check if the device is mobile.
* Returns true if any type of mobile device detected, including special ones
* @param null $userAgent deprecated
* @param null $httpHeaders deprecated
* @return bool
*/
public function isMobile($userAgent = null, $httpHeaders = null)
{
if ($httpHeaders) {
$this->setHttpHeaders($httpHeaders);
}
if ($userAgent) {
$this->setUserAgent($userAgent);
}
$this->setDetectionType(self::DETECTION_TYPE_MOBILE);
if ($this->checkHttpHeadersForMobile()) {
return true;
} else {
return $this->matchDetectionRulesAgainstUA();
}
}
/**
* Check if the device is a tablet.
* Return true if any type of tablet device is detected.
*
* @param string $userAgent deprecated
* @param array $httpHeaders deprecated
* @return bool
*/
public function isTablet($userAgent = null, $httpHeaders = null)
{
$this->setDetectionType(self::DETECTION_TYPE_MOBILE);
foreach (self::$tabletDevices as $_regex) {
if ($this->match($_regex, $userAgent)) {
return true;
}
}
return false;
}
/**
* This method checks for a certain property in the
* userAgent.
* @todo: The httpHeaders part is not yet used.
*
* @param string $key
* @param string $userAgent deprecated
* @param string $httpHeaders deprecated
* @return bool|int|null
*/
public function is($key, $userAgent = null, $httpHeaders = null)
{
// Set the UA and HTTP headers only if needed (eg. batch mode).
if ($httpHeaders) {
$this->setHttpHeaders($httpHeaders);
}
if ($userAgent) {
$this->setUserAgent($userAgent);
}
$this->setDetectionType(self::DETECTION_TYPE_EXTENDED);
return $this->matchUAAgainstKey($key);
}
/**
* Some detection rules are relative (not standard),
* because of the diversity of devices, vendors and
* their conventions in representing the User-Agent or
* the HTTP headers.
*
* This method will be used to check custom regexes against
* the User-Agent string.
*
* @param $regex
* @param string $userAgent
* @return bool
*
* @todo: search in the HTTP headers too.
*/
public function match($regex, $userAgent = null)
{
// Escape the special character which is the delimiter.
$regex = str_replace('/', '\/', $regex);
return (bool) preg_match('/'.$regex.'/is', (!empty($userAgent) ? $userAgent : $this->userAgent));
}
/**
* Get the properties array.
*
* @return array
*/
public static function getProperties()
{
return self::$properties;
}
/**
* Prepare the version number.
*
* @todo Remove the error supression from str_replace() call.
*
* @param string $ver The string version, like "2.6.21.2152";
*
* @return float
*/
public function prepareVersionNo($ver)
{
$ver = str_replace(array('_', ' ', '/'), '.', $ver);
$arrVer = explode('.', $ver, 2);
if (isset($arrVer[1])) {
$arrVer[1] = @str_replace('.', '', $arrVer[1]); // @todo: treat strings versions.
}
return (float) implode('.', $arrVer);
}
/**
* Check the version of the given property in the User-Agent.
* Will return a float number. (eg. 2_0 will return 2.0, 4.3.1 will return 4.31)
*
* @param string $propertyName The name of the property. See self::getProperties() array
* keys for all possible properties.
* @param string $type Either self::VERSION_TYPE_STRING to get a string value or
* self::VERSION_TYPE_FLOAT indicating a float value. This parameter
* is optional and defaults to self::VERSION_TYPE_STRING. Passing an
* invalid parameter will default to the this type as well.
*
* @return string|float The version of the property we are trying to extract.
*/
public function version($propertyName, $type = self::VERSION_TYPE_STRING)
{
if (empty($propertyName)) {
return false;
}
//set the $type to the default if we don't recognize the type
if ($type != self::VERSION_TYPE_STRING && $type != self::VERSION_TYPE_FLOAT) {
$type = self::VERSION_TYPE_STRING;
}
$properties = self::getProperties();
// Check if the property exists in the properties array.
if (array_key_exists($propertyName, $properties)) {
// Prepare the pattern to be matched.
// Make sure we always deal with an array (string is converted).
$properties[$propertyName] = (array) $properties[$propertyName];
foreach ($properties[$propertyName] as $propertyMatchString) {
$propertyPattern = str_replace('[VER]', self::VER, $propertyMatchString);
// Escape the special character which is the delimiter.
$propertyPattern = str_replace('/', '\/', $propertyPattern);
// Identify and extract the version.
preg_match('/'.$propertyPattern.'/is', $this->userAgent, $match);
if (!empty($match[1])) {
$version = ( $type == self::VERSION_TYPE_FLOAT ? $this->prepareVersionNo($match[1]) : $match[1] );
return $version;
}
}
}
return false;
}
/**
* Retrieve the mobile grading, using self::MOBILE_GRADE_* constants.
*
* @return string One of the self::MOBILE_GRADE_* constants.
*/
public function mobileGrade()
{
$isMobile = $this->isMobile();
if (
// Apple iOS 3.2-5.1 - Tested on the original iPad (4.3 / 5.0), iPad 2 (4.3), iPad 3 (5.1), original iPhone (3.1), iPhone 3 (3.2), 3GS (4.3), 4 (4.3 / 5.0), and 4S (5.1)
$this->isIOS() && $this->version('iPad', self::VERSION_TYPE_FLOAT)>=4.3 ||
$this->isIOS() && $this->version('iPhone', self::VERSION_TYPE_FLOAT)>=3.1
){
return self::MOBILE_GRADE_A;
}
if (
$this->isIOS() && $this->version('iPad', self::VERSION_TYPE_FLOAT)<4.3
){
return self::MOBILE_GRADE_B;
}
if (
// Blackberry 4.x - Tested on the Curve 8330
$this->version('BlackBerry', self::VERSION_TYPE_FLOAT)<5.0 ||
// Windows Mobile - Tested on the HTC Leo (WinMo 5.2)
$this->match('MSIEMobile|Windows CE.*Mobile') || $this->version('Windows Mobile', self::VERSION_TYPE_FLOAT)<=5.2
){
return self::MOBILE_GRADE_C;
}
//All older smartphone platforms and featurephones - Any device that doesn't support media queries
//will receive the basic, C grade experience.
return self::MOBILE_GRADE_C;
}
}