setUserAgent($userAgent); } $this->addClientParser('FeedReader'); $this->addClientParser('MobileApp'); $this->addClientParser('MediaPlayer'); $this->addClientParser('PIM'); $this->addClientParser('Browser'); $this->addClientParser('Library'); $this->addDeviceParser('HbbTv'); $this->addDeviceParser('Console'); $this->addDeviceParser('CarBrowser'); $this->addDeviceParser('Camera'); $this->addDeviceParser('PortableMediaPlayer'); $this->addDeviceParser('Mobile'); } public function __call($methodName, $arguments) { foreach (DeviceParserAbstract::getAvailableDeviceTypes() as $deviceName => $deviceType) { if (strtolower($methodName) == 'is'.strtolower(str_replace(' ', '', $deviceName))) { return $this->getDevice() == $deviceType; } } foreach (self::$clientTypes as $client) { if (strtolower($methodName) == 'is'.strtolower(str_replace(' ', '', $client))) { return $this->getClient('type') == $client; } } throw new \BadMethodCallException("Method $methodName not found"); } /** * Sets the useragent to be parsed * * @param string $userAgent */ public function setUserAgent($userAgent) { if ($this->userAgent != $userAgent) { $this->reset(); } $this->userAgent = $userAgent; } protected function reset() { $this->bot = null; $this->client = null; $this->device = null; $this->os = null; $this->brand = ''; $this->model = ''; $this->parsed = false; } /** * @var ClientParserAbstract[] */ protected $clientParsers = array(); /** * @param ClientParserAbstract|string $parser * @throws \Exception */ public function addClientParser($parser) { if (is_string($parser) && class_exists('DeviceDetector\\Parser\\Client\\'.$parser)) { $className = 'DeviceDetector\\Parser\\Client\\'.$parser; $parser = new $className(); } if ($parser instanceof ClientParserAbstract) { $this->clientParsers[] = $parser; self::$clientTypes[] = $parser->getName(); return; } throw new \Exception('client parser not found'); } public function getClientParsers() { return $this->clientParsers; } /** * @var DeviceParserAbstract[] */ protected $deviceParsers = array(); /** * @param DeviceParserAbstract|string $parser * @throws \Exception */ public function addDeviceParser($parser) { if (is_string($parser) && class_exists('DeviceDetector\\Parser\\Device\\'.$parser)) { $className = 'DeviceDetector\\Parser\\Device\\'.$parser; $parser = new $className(); } if ($parser instanceof DeviceParserAbstract) { $this->deviceParsers[] = $parser; return; } throw new \Exception('device parser not found'); } public function getDeviceParsers() { return $this->deviceParsers; } /** * Sets whether to discard additional bot information * If information is discarded it's only possible check whether UA was detected as bot or not. * (Discarding information speeds up the detection a bit) * * @param bool $discard */ public function discardBotInformation($discard=true) { $this->discardBotInformation = $discard; } /** * Sets whether to skip bot detection. * It is needed if we want bots to be processed as a simple clients. So we can detect if it is mobile client, * or desktop, or enything else. By default all this information is not retrieved for the bots. * * @param bool $skip */ public function skipBotDetection($skip=true) { $this->skipBotDetection = $skip; } /** * Returns if the parsed UA was identified as a Bot * * @see bots.yml for a list of detected bots * * @return bool */ public function isBot() { return !empty($this->bot); } /** * Returns if the parsed UA was identified as a touch enabled device * * Note: That only applies to windows 8 tablets * * @return bool */ public function isTouchEnabled() { $regex = 'Touch'; return $this->matchUserAgent($regex); } /** * Returns if the parsed UA contains the 'Android; Tablet;' fragment * * @return bool */ protected function hasAndroidTableFragment() { $regex = 'Android( [\.0-9]+)?; Tablet;'; return $this->matchUserAgent($regex); } /** * Returns if the parsed UA contains the 'Android; Mobile;' fragment * * @return bool */ protected function hasAndroidMobileFragment() { $regex = 'Android( [\.0-9]+)?; Mobile;'; return $this->matchUserAgent($regex); } public function isMobile() { if (!empty($this->device) && in_array($this->device, array( DeviceParserAbstract::DEVICE_TYPE_FEATURE_PHONE, DeviceParserAbstract::DEVICE_TYPE_SMARTPHONE, DeviceParserAbstract::DEVICE_TYPE_TABLET, DeviceParserAbstract::DEVICE_TYPE_PHABLET, DeviceParserAbstract::DEVICE_TYPE_CAMERA, DeviceParserAbstract::DEVICE_TYPE_PORTABLE_MEDIA_PAYER, ))) { return true; } $osShort = $this->getOs('short_name'); if (empty($osShort) || self::UNKNOWN == $osShort) { return false; } return !$this->isBot() && !$this->isDesktop(); } /** * Returns if the parsed UA was identified as desktop device * Desktop devices are all devices with an unknown type that are running a desktop os * * @see self::$desktopOsArray * * @return bool */ public function isDesktop() { $osShort = $this->getOs('short_name'); if (empty($osShort) || self::UNKNOWN == $osShort) { return false; } $decodedFamily = OperatingSystem::getOsFamily($osShort); return in_array($decodedFamily, self::$desktopOsArray); } /** * Returns the operating system data extracted from the parsed UA * * If $attr is given only that property will be returned * * @param string $attr property to return(optional) * * @return array|string */ public function getOs($attr = '') { if ($attr == '') { return $this->os; } if (!isset($this->os[$attr])) { return self::UNKNOWN; } return $this->os[$attr]; } /** * Returns the client data extracted from the parsed UA * * If $attr is given only that property will be returned * * @param string $attr property to return(optional) * * @return array|string */ public function getClient($attr = '') { if ($attr == '') { return $this->client; } if (!isset($this->client[$attr])) { return self::UNKNOWN; } return $this->client[$attr]; } /** * Returns the device type extracted from the parsed UA * * @see DeviceParserAbstract::$deviceTypes for available device types * * @return int|null */ public function getDevice() { return $this->device; } /** * Returns the device type extracted from the parsed UA * * @see DeviceParserAbstract::$deviceTypes for available device types * * @return string */ public function getDeviceName() { if ($this->getDevice() !== null) { return DeviceParserAbstract::getDeviceName($this->getDevice()); } return ''; } /** * Returns the device brand extracted from the parsed UA * * @see self::$deviceBrand for available device brands * * @return string */ public function getBrand() { return $this->brand; } /** * Returns the full device brand name extracted from the parsed UA * * @see self::$deviceBrand for available device brands * * @return string */ public function getBrandName() { return DeviceParserAbstract::getFullName($this->getBrand()); } /** * Returns the device model extracted from the parsed UA * * @return string */ public function getModel() { return $this->model; } /** * Returns the user agent that is set to be parsed * * @return string */ public function getUserAgent() { return $this->userAgent; } /** * Returns the bot extracted from the parsed UA * * @return array */ public function getBot() { return $this->bot; } protected $parsed = false; /** * Triggers the parsing of the current user agent */ public function parse() { if ($this->parsed) { return; } $this->parsed = true; // skip parsing for empty useragents or those not containing any letter if (empty($this->userAgent) || !preg_match('/([a-z])/i', $this->userAgent)) { return; } $this->parseBot(); if ($this->isBot()) { return; } $this->parseOs(); /** * Parse Clients * Clients might be browsers, Feed Readers, Mobile Apps, Media Players or * any other application accessing with an parseable UA */ $this->parseClient(); $this->parseDevice(); } /** * Parses the UA for bot information using the Bot parser */ protected function parseBot() { if ($this->skipBotDetection) { $this->bot = false; return false; } $botParser = new Bot(); $botParser->setUserAgent($this->getUserAgent()); $botParser->setCache($this->getCache()); if ($this->discardBotInformation) { $botParser->discardDetails(); } $this->bot = $botParser->parse(); } protected function parseClient() { $parsers = $this->getClientParsers(); foreach ($parsers as $parser) { $parser->setCache($this->getCache()); $parser->setUserAgent($this->getUserAgent()); $client = $parser->parse(); if (!empty($client)) { $this->client = $client; break; } } } protected function parseDevice() { $parsers = $this->getDeviceParsers(); foreach ($parsers as $parser) { $parser->setCache($this->getCache()); $parser->setUserAgent($this->getUserAgent()); if ($parser->parse()) { $this->device = $parser->getDeviceType(); $this->model = $parser->getModel(); $this->brand = $parser->getBrand(); break; } } /** * If no brand has been assigned try to match by known vendor fragments */ if (empty($this->brand)) { $vendorParser = new VendorFragment($this->getUserAgent()); $this->brand = $vendorParser->parse(); } $osShortName = $this->getOs('short_name'); $osFamily = OperatingSystem::getOsFamily($osShortName); $osVersion = $this->getOs('version'); $clientName = $this->getClient('name'); /** * Chrome on Android passes the device type based on the keyword 'Mobile' * If it is present the device should be a smartphone, otherwise it's a tablet * See https://developer.chrome.com/multidevice/user-agent#chrome_for_android_user_agent */ if (is_null($this->device) && $osFamily == 'Android' && in_array($this->getClient('name'), array('Chrome', 'Chrome Mobile'))) { if ($this->matchUserAgent('Chrome/[\.0-9]* Mobile')) { $this->device = DeviceParserAbstract::DEVICE_TYPE_SMARTPHONE; } else if ($this->matchUserAgent('Chrome/[\.0-9]* (?!Mobile)')) { $this->device = DeviceParserAbstract::DEVICE_TYPE_TABLET; } } /** * Some user agents simply contain the fragment 'Android; Tablet;', so we assume those devices as tablets */ if (is_null($this->device) && $this->hasAndroidTableFragment()) { $this->device = DeviceParserAbstract::DEVICE_TYPE_TABLET; } /** * Some user agents simply contain the fragment 'Android; Mobile;', so we assume those devices as smartphones */ if (is_null($this->device) && $this->hasAndroidMobileFragment()) { $this->device = DeviceParserAbstract::DEVICE_TYPE_SMARTPHONE; } /** * Android up to 3.0 was designed for smartphones only. But as 3.0, which was tablet only, was published * too late, there were a bunch of tablets running with 2.x * With 4.0 the two trees were merged and it is for smartphones and tablets * * So were are expecting that all devices running Android < 2 are smartphones * Devices running Android 3.X are tablets. Device type of Android 2.X and 4.X+ are unknown */ if (is_null($this->device) && $osShortName == 'AND' && $osVersion != '') { if (version_compare($osVersion, '2.0') == -1) { $this->device = DeviceParserAbstract::DEVICE_TYPE_SMARTPHONE; } elseif (version_compare($osVersion, '3.0') >= 0 and version_compare($osVersion, '4.0') == -1) { $this->device = DeviceParserAbstract::DEVICE_TYPE_TABLET; } } /** * All detected feature phones running android are more likely a smartphone */ if ($this->device == DeviceParserAbstract::DEVICE_TYPE_FEATURE_PHONE && $osFamily == 'Android') { $this->device = DeviceParserAbstract::DEVICE_TYPE_SMARTPHONE; } /** * According to http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx * Internet Explorer 10 introduces the "Touch" UA string token. If this token is present at the end of the * UA string, the computer has touch capability, and is running Windows 8 (or later). * This UA string will be transmitted on a touch-enabled system running Windows 8 (RT) * * As most touch enabled devices are tablets and only a smaller part are desktops/notebooks we assume that * all Windows 8 touch devices are tablets. */ if (is_null($this->device) && ($osShortName == 'WRT' || ($osShortName == 'WIN' && version_compare($osVersion, '8.0'))) && $this->isTouchEnabled()) { $this->device = DeviceParserAbstract::DEVICE_TYPE_TABLET; } /** * All devices running Opera TV Store are assumed to be a tv */ if ($this->matchUserAgent('Opera TV Store')) { $this->device = DeviceParserAbstract::DEVICE_TYPE_TV; } /** * Devices running Kylo or Espital TV Browsers are assumed to be a TV */ if (is_null($this->device) && in_array($clientName, array('Kylo', 'Espial TV Browser'))) { $this->device = DeviceParserAbstract::DEVICE_TYPE_TV; } // set device type to desktop for all devices running a desktop os that were not detected as an other device type if (is_null($this->device) && $this->isDesktop()) { $this->device = DeviceParserAbstract::DEVICE_TYPE_DESKTOP; } } protected function parseOs() { $osParser = new OperatingSystem(); $osParser->setUserAgent($this->getUserAgent()); $osParser->setCache($this->getCache()); $this->os = $osParser->parse(); } protected function matchUserAgent($regex) { $regex = '/(?:^|[^A-Z_-])(?:' . str_replace('/', '\/', $regex) . ')/i'; if (preg_match($regex, $this->userAgent, $matches)) { return $matches; } return false; } /** * Parses a useragent and returns the detected data * * ATTENTION: Use that method only for testing or very small applications * To get fast results from DeviceDetector you need to make your own implementation, * that should use one of the caching mechanisms. See README.md for more information. * * @internal * @deprecated * * @param string $ua UserAgent to parse * * @return array */ public static function getInfoFromUserAgent($ua) { $deviceDetector = new DeviceDetector($ua); $deviceDetector->parse(); if ($deviceDetector->isBot()) { return array( 'user_agent' => $deviceDetector->getUserAgent(), 'bot' => $deviceDetector->getBot() ); } $osFamily = OperatingSystem::getOsFamily($deviceDetector->getOs('short_name')); $browserFamily = \DeviceDetector\Parser\Client\Browser::getBrowserFamily($deviceDetector->getClient('short_name')); $processed = array( 'user_agent' => $deviceDetector->getUserAgent(), 'os' => $deviceDetector->getOs(), 'client' => $deviceDetector->getClient(), 'device' => array( 'type' => $deviceDetector->getDeviceName(), 'brand' => $deviceDetector->getBrand(), 'model' => $deviceDetector->getModel(), ), 'os_family' => $osFamily !== false ? $osFamily : 'Unknown', 'browser_family' => $browserFamily !== false ? $browserFamily : 'Unknown', ); return $processed; } /** * Sets the Cache class * * @param Cache|\Doctrine\Common\Cache\CacheProvider $cache * @throws \Exception */ public function setCache($cache) { if ($cache instanceof Cache || (class_exists('\Doctrine\Common\Cache\CacheProvider') && $cache instanceof \Doctrine\Common\Cache\CacheProvider)) { $this->cache = $cache; return; } throw new \Exception('Cache not supported'); } /** * Returns Cache object * * @return \Doctrine\Common\Cache\CacheProvider */ public function getCache() { if (!empty($this->cache)) { return $this->cache; } return new StaticCache(); } }