<?php 
/**
* @version		$Id: browser.helper.php 2500 2023-04-23 16:23:34Z IOS $
* @package		Elxis
* @subpackage	Helpers / Browser detection
* @copyright	Copyright (c) 2006-2023 Elxis CMS (https://www.elxis.org). All rights reserved.
* @license		Elxis Public License ( https://www.elxis.org/elxis-public-license.html )
* @author		Elxis Team ( https://www.elxis.org )
* @description 	Elxis CMS is free software. Read the license for copyright notices and details
*
* data sources 	
				http://www.webuseragents.com/feed-reader
				http://www.webuseragents.com/robot
				https://user-agents.me/cfnetwork-version-list
				https://en.wikipedia.org/wiki/Comparison_of_mobile_operating_systems
				https://www.whatismybrowser.com/developers/tools/user-agent-parser/browse
				https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox
				https://msdn.microsoft.com/en-us/library/ms537503(v=vs.85).aspx
				https://developer.chrome.com/multidevice/user-agent
				http://www.webapps-online.com/online-tools/user-agent-strings/dv/device5/tv
				https://udger.com/resources/ua-list
				http://stackoverflow.com/questions/26528668/detecting-the-stock-android-browser-with-php
* inpired by
				https://github.com/lennerd/vipx-bot-detect
				https://github.com/piwik/device-detector
*/

defined('_ELXIS_') or die ('Direct access to this location is not allowed');


class elxisBrowserHelper {

	private $useragent = '';
	private $device = array(
		'type' => '', //desktop, mobile, tablet, tv, car, robot, feedreader, emailclient, ...
		'name' => '',
		'version' => '',
		'os_platform' => '',
		'os_name' => '',
		'os_version' => '',
		'extra' => array()
	);


	/*********************/
	/* MAGIC CONSTRUCTOR */
	/*********************/
	public function __construct() {
	}


	/********************/
	/* GET BROWSER INFO */
	/********************/
	public function getBrowser($useragent='', $compat=true) {
		if (trim($useragent) == '') {
			if (isset($_SERVER['HTTP_USER_AGENT'])) {
				$useragent = eFactory::getElxis()->sanitizeVar($_SERVER['HTTP_USER_AGENT']);
			} else {
				$useragent = '';
			}
		}
		if (($useragent != '') && ($useragent == $this->useragent)) { //previously fully analized
			return $this->getBrowserResponse($useragent, $compat);
		}

		$this->init($useragent);
		if ($useragent == '') {
			return $this->getBrowserResponse($useragent, $compat);
		}

		$this->detect();

		return $this->getBrowserResponse($useragent, $compat);
	}


	/****************************/
	/* MAKE getBrowser RESPONSE */
	/****************************/
	private function getBrowserResponse($useragent, $compat=true) {
		//No compatibility with previous Elxis versions (4.5 r1889+)
		if (!$compat) {
			$browser_info = $this->device;
			$browser_info['useragent'] = $useragent;
			return $browser_info;
		}

		//Elxis 4.0-4.5r1888 compatibility
		$browser_info = array(
			'agent' => $useragent,
			'browser' => $this->device['name'],
			'version' => $this->device['version'],
			'os' => '',//deprecated
			'os_name' => $this->device['os_name'],
			'os_version' => $this->device['os_version'],
			'platform' => $this->device['os_platform'],//warning: previously OS family, now architecture
			'mobile' => false,
			'robot' => false,
			'aol' => false,//deprecated
			'aol_version' => ''//deprecated
		);

		if ($useragent != '') {
			$browser_info['mobile'] = $this->isMobile($useragent);
		}
		
		$browser_info['robot'] = ($this->device['type'] == 'robot') ? true : false;

		//Elxis 4.5 r1888+ device
		$browser_info['useragent'] = $useragent;
		foreach ($this->device as $k => $v) {
			$browser_info[$k] = $v;
		}

		return $browser_info;
	}


	/******************************/
	/* DETECT BROWSER/OS/PLATFORM */
	/******************************/
	private function detect() {
		if ($this->checkRobot($this->useragent) === true) { return; } //found type "robot", dont proceed further
		if ($this->checkFeedReader($this->useragent) === true) { return; } //found type "feedreader"
		if ($this->checkValidator($this->useragent) === true) { return; } //found type "validator"
		if ($this->checkLibrary($this->useragent) === true) { return; } //found type "library"
		if ($this->checkMediaPlayer($this->useragent) === true) { return; } //found type "mediaplayer"
		if ($this->checkEmailClient($this->useragent) === true) { return; } //found type "emailclient"
		if ($this->checkBrowser($this->useragent) === true) { //found type "desktop/mobile/tv etc"
			//includes OS check and platform check
			return; 
		}
	}


	/*****************************/
	/* INITIALIZE ALL PROPERTIES */
	/*****************************/
	private function init($useragent='') {
		$this->useragent = $useragent;
		$this->device = array(
			'type' => '', //desktop, mobile, tablet, tv, car, robot, feedreader, ...
			'name' => '',
			'version' => '',
			'os_platform' => '',
			'os_name' => '',
			'os_version' => '',
			'extra' => array()
		);
	}


	/**************************************************/
	/* PERFORM REGULAR EXPRESION SEARCH ON USER AGENT */
	/**************************************************/
	private function matchUserAgent($useragent, $regex) {
		if (!@preg_match('#'.$regex.'#i', $useragent, $matches)) { return false; }
		return $matches;
    }


	/*********************************/
	/* CHECK IF IT IS A ROBOT/SPIDER */
	/*********************************/
	private function checkRobot($useragent) {
		$found = false;
		$patterns = $this->getRobotPatterns();
		foreach ($patterns as $pat) {
			$matches = $this->matchUserAgent($useragent, $pat[0]);
			if (!$matches) { continue; }
			$this->device['type'] = 'robot';
			$this->device['name'] = $pat[1];
			$found = true;
			break;
		}
		return $found;
	}


	/********************************/
	/* CHECK IF IT IS A FEED READER */
	/********************************/
	private function checkFeedReader($useragent) {
		$found = false;
		$patterns = $this->getFeedPatterns();
		foreach ($patterns as $pat) {
			$matches = $this->matchUserAgent($useragent, $pat[0]);
			if (!$matches) { continue; }
			$this->device['type'] = 'feedreader';
			$this->device['name'] = $pat[1];
			$found = true;
			break;
		}
		return $found;
	}


	/********************************************/
	/* CHECK IF IT IS A (W3C) VALIDATOR SERVICE */
	/********************************************/
	private function checkValidator($useragent) {
		$found = false;
		$patterns = array(
			array('W3C_*Validator', 'W3C Validator'),
			array('online link validator', 'Online link'),
			array('JCheckLinks', 'JCheckLinks'),
			array('CheckLinks\/', 'CheckLinks'),
			array('LongURL\sAPI', 'LongURL'),
			array('FeedValidator', 'W3C Feed Validator'),
			array('FeedValidator', 'W3C Feed Validator'),
			array('W3C-checklink\/', 'W3C Checklink'),
			array('HTMLParser\/', 'HTMLParser'),
			array('Xenu\sLink\sSleuth\/', 'Xenu'),
		);
		foreach ($patterns as $pat) {
			$matches = $this->matchUserAgent($useragent, $pat[0]);
			if (!$matches) { continue; }
			$this->device['type'] = 'validator';
			$this->device['name'] = $pat[1];
			$found = true;
			break;
		}
		return $found;
	}


	/**************************************/
	/* CHECK IF IT IS A LIBRARY LIKE WGET */
	/**************************************/
	private function checkLibrary($useragent) {
		$found = false;
		$patterns = array(
			array('libcurl', 'cURL'),
			array('Elxis\sCURL', 'Elxis cURL'),
			array('perlclient|libwww-perl', 'Perl'),
			array('winhttp', 'WinHTTP'),
			array('Java\/', 'Java'),
			array('Python-urllib|Python-webchecker|python-requests', 'Python'),
			array('Snoopy', 'Snoopy'),
			array('Wget\/', 'Wget'),
			array('Go\s1\.1\spackage\shttp|Go\shttp\spackage|Go\-http\-client', 'Go language'),
			array('Indy\sLibrary', 'Indy')
		);
		foreach ($patterns as $pat) {
			$matches = $this->matchUserAgent($useragent, $pat[0]);
			if (!$matches) { continue; }
			$this->device['type'] = 'library';
			$this->device['name'] = $pat[1];
			$found = true;
			break;
		}
		return $found;
	}


	/*********************************/
	/* CHECK IF IT IS A MEDIA PLAYER */
	/*********************************/
	private function checkMediaPlayer($useragent) {
		$found = false;
		$patterns = array(
			array('iTunes', 'iTunes'),
			array('QuickTime', 'QuickTime'),
			array('Songbird', 'Songbird'),
			array('FlyCast', 'FlyCast'),
			array('Windows-Media-Player', 'Windows Media Player'),
			array('VLC\smedia\splayer', 'VLC media player'),
			array('Winamp', 'Winamp'),
			array('XBMC', 'XBMC'),
			array('Banshee', 'Banshee'),
			array('Boxxe', 'Boxxe'),
			array('Dailymotion', 'Dailymotion'),
			array('foobar2000', 'foobar2000'),
			array('GOM\sPlayer', 'GOM Player'),
			array('MPlayer2', 'MPlayer2'),
			array('MPlayer', 'MPlayer'),
			array('Plex\sMedia\sCenter', 'Plex Media Center'),
			array('Instacast', 'Instacast'),
			array('SubStream', 'SubStream'),
			array('XMPlay', 'XMPlay')
		);
		foreach ($patterns as $pat) {
			$matches = $this->matchUserAgent($useragent, $pat[0]);
			if (!$matches) { continue; }
			$this->device['type'] = 'mediaplayer';
			$this->device['name'] = $pat[1];
			$found = true;
			break;
		}
		return $found;
	}


	/**********************************/
	/* CHECK IF IT IS AN EMAIL CLIENT */
	/**********************************/
	private function checkEmailClient($useragent) {
		$found = false;
		$patterns = array(
			array('Outlook-Express', 'Windows Live Mail'),
			array('Thunderbird', 'Thunderbird'),
			array('Airmail', 'Airmail'),
			array('Microsoft\sOutlook', 'Microsoft Outlook'),
			array('Lotus-Notes', 'Lotus Notes'),
			array('The\sBat!', 'The Bat!'),
			array('PocoMail', 'PocoMail')
		);
		foreach ($patterns as $pat) {
			$matches = $this->matchUserAgent($useragent, $pat[0]);
			if (!$matches) { continue; }
			$this->device['type'] = 'emailclient';
			$this->device['name'] = $pat[1];
			$found = true;
			break;
		}
		return $found;
	}


	/*************************************/
	/* CHECK IF IT IS A STANDARD BROWSER */
	/*************************************/
	private function checkBrowser($useragent) {
		$browsers = array(
			'Edge' => 'Windows',//edge needs to be placed first, else might be detected as Chrome.
			'Small' => 'Android',//check small (mobile mostly) browsers first as they contain strings like safari, opera, etc.
			'Other' => 'Linux',//same as for "Small" browsers
			'Chrome' => 'Windows',
			'Firefox' => 'Windows',
			'IE' => 'Windows',//internet explorer
			'Safari' => 'Mac',
			'Opera' => 'Mac',
			'Mozilla' => 'Linux'//check last due to open standard
		);

		$found = false;
		$os_found = false;
		foreach ($browsers as $browser => $possible_os) {
			$funcBR = 'checkBrowser'.$browser;
			$found = $this->$funcBR($useragent);
			if (!$found) { continue; }

			$os_found = false;
			if ($possible_os != '') {
				$funcOS = 'checkOs'.$possible_os;
				$os_found = $this->$funcOS($useragent);
			}
			if (!$os_found) {
				$os_found = $this->checkAllOperatingSystems($useragent, $possible_os);//check all operating systems except possible os
			}
			$this->checkPlatform($useragent);
			break;
		}

		if (!$found) {//browser not found, try at least to find operating system
			if (!$os_found) {
				$this->checkAllOperatingSystems($useragent, '');
				$this->checkPlatform($useragent);
			}
		}
	}


	/******************/
	/* CHECK PLATFORM */
	/******************/
	private function checkPlatform($useragent) {
		if ($this->matchUserAgent($useragent, 'WOW64|x64|win64|amd64|x86_64')) {
			$this->device['os_platform'] = 'x64';
		} else if ($this->matchUserAgent($useragent, 'i[0-9]86|i86pc')) {
			$this->device['os_platform'] = 'x86';
		} else if (stripos($useragent, 'arm')) {
			$this->device['os_platform'] = 'ARM';
		} else {
			$this->device['os_platform'] = '';
		}
    }


	private function checkBrowserEdge($useragent) {
		$regex_mobile = 'Windows Phone .*(Edge)/(\\d+)\\.(\\d+)';
		$regex = '(Edge)/(\\d+)\\.(\\d+)';
		$matches = $this->matchUserAgent($useragent, $regex_mobile);
		if ($matches) {
			$this->device['type'] = 'mobile';
			$this->device['name'] = 'Edge';
			$this->device['version'] = $matches[2].'.'.$matches[3];
			return true;
		}
		$matches = $this->matchUserAgent($useragent, $regex);
		if ($matches) {
			$this->device['type'] = 'desktop';
			$this->device['name'] = 'Edge';
			$this->device['version'] = $matches[2].'.'.$matches[3];
			return true;
		}
		return false;
	}


	private function checkBrowserFirefox($useragent) {
		$is_firefox = false;
		$version = '';
		if (preg_match("#Firefox[\/ \(]([^ ;\)]+)#i", $useragent, $matches)) {
			$is_firefox = true;
			$version = (isset($matches[1])) ? $matches[1] : '';
		} else if (preg_match("#FxiOS/(\d+[\.\d]+)#i", $useragent, $matches)) {
			$is_firefox = true;
			$version = (isset($matches[1])) ? 'FxiOS '.$matches[1] : 'FxiOS';
		} else if (preg_match("#Firefox#i", $useragent, $matches)) {
			$is_firefox = true;
			$version = '';
		}

		if (!$is_firefox) { return; }

		if (preg_match('/^[0-9\.]+$/', $version)) {
			$parts = explode('.', $version);
			$version = $parts[0];
			if (isset($parts[1])) { $version .= '.'.$parts[1]; }
		}

		$this->device['name'] = 'Firefox';
		$this->device['version'] = $version;

		$is_tv = $this->deviceIsTV($useragent);
		if ($is_tv) {
			$this->device['type'] = 'tv';
		} else if (stripos($useragent, 'Mobile') !== false) {
			$this->device['type'] = 'mobile';
		} else if (stripos($useragent, 'Tablet') !== false) {
			$this->device['type'] = 'tablet';
		} else {
			$this->device['type'] = 'desktop';
		}

		return true;
	}


	private function checkBrowserIE($useragent) {
		$patterns = array(
			//mobile
			array('IEMobile[ /](\d+[\.\d]+)', 'IE Mobile', 1),
			array('MSIE (\d+[\.\d]+).*XBLWP7', 'IE Mobile', 1),
			//desktop
			array('MSIE.*Trident/4.0', 'Internet Explorer', '8.0'),
			array('MSIE.*Trident/5.0', 'Internet Explorer', '9.0'),
			array('MSIE.*Trident/6.0', 'Internet Explorer', '10.0'),
			array('MSIE.*Trident/7.0', 'Internet Explorer', '11.0'),
			array('MSIE (\d+[\.\d]+)', 'Internet Explorer', 1),
			array('IE[ /](\d+[\.\d]++)', 'Internet Explorer', 1)
		);

		$found = false;
		foreach ($patterns as $pat) {
			$matches = $this->matchUserAgent($useragent, $pat[0]);
			if (!$matches) { continue; }
			if (stripos($useragent, 'Tablet PC') !== false) {
				$this->device['type'] = 'tablet';
			} else {
				$this->device['type'] = ($pat[1] == 'IE Mobile') ? 'mobile' : 'desktop';
			}
			$this->device['name'] = $pat[1];
			$this->device['version'] = $this->versionFromMatches($pat[2], $matches);
			$found = true;
			break;
		}
		return $found;
	}


	private function checkBrowserSafari($useragent) {
		$patterns = array(
			//mobile
			array('(?:(?:iPod|iPad|iPhone).+Version|MobileSafari)/(\d+[\.\d]+)', 'Mobile Safari', 1),
			array('Version/(\d+[\.\d]+).*Mobile.*Safari/', 'Mobile Safari', 1),
			array('(?:iPod|iPhone|iPad)', 'Mobile Safari', ''),
			//desktop
			array('Version/(\d+[\.\d]+).*Safari/|Safari/\d+', 'Safari', 1)
		);

		$found = false;
		foreach ($patterns as $pat) {
			$matches = $this->matchUserAgent($useragent, $pat[0]);
			if (!$matches) { continue; }
			if (stripos($useragent, 'iPad') !== false) {
				$this->device['type'] = 'tablet';
			} else {
				$this->device['type'] = ($pat[1] == 'Mobile Safari') ? 'mobile' : 'desktop';
			}
			$this->device['name'] = $pat[1];
			$this->device['version'] = $this->versionFromMatches($pat[2], $matches);
			$found = true;
			break;
		}
		return $found;
	}


	private function checkBrowserChrome($useragent) {
		$patterns = array(
			//mobile
			array('CrMo(?:/(\d+[\.\d]+))?', 'Chrome Mobile', 1),
			array('CriOS(?:/(\d+[\.\d]+))?', 'Chrome Mobile iOS', 1),
			array('Chrome(?:/(\d+[\.\d]+))? Mobile', 'Chrome Mobile', 1),
			//desktop/tablet/TV
			array('Chrome(?:/(\d+[\.\d]+))?', 'Chrome', 1)
		);

		$found = false;
		foreach ($patterns as $pat) {
			$matches = $this->matchUserAgent($useragent, $pat[0]);
			if (!$matches) { continue; }
			$is_tv = $this->deviceIsTV($useragent);
			if ($is_tv) {
				$this->device['type'] = 'tv';
			} else if (($pat[1] == 'Chrome Mobile') || ($pat[1] == 'Chrome Mobile iOS')) {
				$this->device['type'] = 'mobile';
			} else {
				$this->device['type'] = (stripos($useragent, 'Android') === false) ? 'desktop' : 'tablet';
			}
			$this->device['name'] = $pat[1];
			$this->device['version'] = $this->versionFromMatches($pat[2], $matches);
			$found = true;
			break;
		}
		return $found;
	}


	private function checkBrowserOpera($useragent) {
		$patterns = array(
			//mobile
			array('(?:Opera Tablet.*Version)/(\d+[\.\d]+)', 'Opera Mobile', 1),
			array('Opera Mini/(?:att/)?(\d+[\.\d]+)', 'Opera Mini', 1),
			array('(Opera/.+Opera Mobi.+Version)/(\d+[\.\d]+)', 'Opera Mobile', 1),
			array('(Mobile.+OPR)/(\d+[\.\d]+)', 'Opera Mobile', 1),
			//desktop/tablet/TV
			array('Opera.+Edition Next.+Version/(\d+[\.\d]+)', 'Opera Next', 1),
			array('(?:Opera|OPR)[/ ](?:9.80.*Version/)?(\d+[\.\d]+).+Edition Next', 'Opera Next', 1),
			array('(?:Opera|OPR)[/ ]?(?:9.80.*Version/)?(\d+[\.\d]+)', 'Opera', 1)
		);


//Opera/9.80 (J2ME/MIDP; Opera Mini/9.80 (S60; SymbOS; Opera Mobi/23.348; U; en) Presto/2.5.25 Version/10.54

		$found = false;
		foreach ($patterns as $pat) {
			$matches = $this->matchUserAgent($useragent, $pat[0]);
			if (!$matches) { continue; }
			$is_tv = $this->deviceIsTV($useragent);
			if ($is_tv) {
				$this->device['type'] = 'tv';
			} else if (($pat[1] == 'Opera Mobile') || ($pat[1] == 'Opera Mini')) {
				$this->device['type'] = 'mobile';
			} else {
				if ((stripos($useragent, 'Tablet') !== false) || (stripos($useragent, 'Android') !== false)) {
					$this->device['type'] = 'tablet';
				} else if (stripos($useragent, 'Coast') !== false) {//Opera Coast for iphone
					$this->device['type'] = 'mobile';
				} else {
					$this->device['type'] = 'desktop';
				}
			}
			$this->device['name'] = $pat[1];
			$this->device['version'] = $this->versionFromMatches($pat[2], $matches);
			$found = true;
			break;
		}
		return $found;
	}


	private function checkBrowserSmall($useragent) {
		$patterns = array(
			//mobile
			array('Dolfin(?:/(\d+[\.\d]+))?|dolphin', 'Dolphin', 1),
			array('flynx', 'Flynx', ''),
			array('GhosteryBrowser-Android(?:/v(\d+[\.\d]+))?|GhosteryBrowser', 'Ghostery', 1),
			array('Mercury(?:/(\d+[\.\d]+))?', 'Mercury', 1),
			array('Puffin(?:/(\d+[\.\d]+))?', 'Puffin', 1),
			array('UC[ ]?Browser(?:[ /]?(\d+[\.\d]+))?', 'UC Browser', 1),
			array('UCWEB(?:[ /]?(\d+[\.\d]+))?', 'UC Browser', 1),
			array('Skyfire(?:/(\d+[\.\d]+))?|Skyfire', 'Skyfire', 1),
			array('Bolt(?:/(\d+[\.\d]+))?', 'Bolt', 1),
			array('TeaShark(?:/(\d+[\.\d]+))?', 'TeaShark', 1),
			array('Blazer(?:/(\d+[\.\d]+))?|Blazer', 'Blazer', 1),
			array('(Tizen\/|Tizen\s|Tizen\sBrowser\/)(\d+[\.\d]+)?', 'Tizen', 2),
			array('OmniWeb(?:/[v]?(\d+[\.\d]+))?', 'OmniWeb', 1),
			array('SailfishBrowser(?:/(\d+[\.\d]+))?', 'Sailfish', 1),
			array('Fennec(?:/(\d+[\.\d]+))?', 'Fennec', 1),
			array('Silk(?:/(\d+[\.\d]+))?', 'Silk', 1),
			array('(BB10|BlackBerry|PlayBook).+Version/(\d+[\.\d]+)', 'BlackBerry', 2),
			array('BlackBerry', 'BlackBerry', ''),
			array('(?:Maxthon|MxBrowser)[ /](\d+[\.\d]+)', 'Maxthon', 1),//mobile/desktop
			array('ACHEETAHI', 'CM Browser', ''),
			array('Brave(?:/(\d+[\.\d]+))?', 'Brave', 1),
			array('Openwave(?:/(\d+[\.\d]+))?', 'Openwave', 1),
			array('UP.Browser(?:/(\d+[\.\d]+))?', 'Openwave', 1),
			array('Firefox.*Tablet browser (\d+[\.\d]+)', 'MicroB', 1),
			array('Maemo Browser(?: (\d+[\.\d]+))?', 'MicroB', 1),
			array('PLAYSTATION|NINTENDO\s3', 'Playstation', ''),
			array('NetFront', 'NetFront', ''),
			array('(?:Polaris|Embider)(?:[/ ](\d+[\.\d]+))?', 'Polaris', 1)
		);

		$found = false;
		foreach ($patterns as $pat) {
			$matches = $this->matchUserAgent($useragent, $pat[0]);
			if (!$matches) { continue; }
			$is_tv = $this->deviceIsTV($useragent);
			if ($is_tv) {
				$this->device['type'] = 'tv';
			} else if ($pat[1] == 'Maxthon') {
				$this->device['type'] = (stripos($useragent, 'Android') === false) ? 'desktop' : 'mobile';
			} else if ($pat[1] == 'MicroB') {
				$this->device['type'] = 'tablet';
			} else {
				$this->device['type'] = 'mobile';
			}
			$this->device['name'] = $pat[1];
			$this->device['version'] = $this->versionFromMatches($pat[2], $matches);
			$found = true;
			break;
		}

		if (!$found) {
			//Stock Android browser
			preg_match('#Android.*AppleWebKit\/([\d.]+)#i', $useragent, $matches);
			if ($matches) {
				if (isset($matches[0]) && isset($matches[1]) && (intval($matches[1]) < 537)) {
					$this->device['type'] = 'mobile';
					$this->device['name'] = 'Android browser';
					$this->device['version'] = '';
					$found = true;
				}
			}
		}

		return $found;
	}


	private function checkBrowserOther($useragent) {
		$pattern = '(?:Avant\sBrowser|Kapiko|Fireweb|Conkeror|Vivaldi|Midori|AmigaVoyager|Amiga-Aweb|CometBird|Flock|Swiftfox|BaiduBrowser|Kindle|QupZilla|Lynx)';
		preg_match("#".$pattern."#i", $useragent, $matches);
		if ($matches) {
			$this->device['type'] = ($matches[0] == 'Kindle') ? 'tablet' : 'desktop';
			$this->device['name'] = $matches[0];
			$this->device['version'] = '';
			return true;
		}

		$patterns = array(
			array('Konqueror(?:/(\d+[\.\d]+))?', 'Konqueror', 1),
			array('Epiphany(?:/(\d+[\.\d]+))?', 'Epiphany', 1),
			array('PaleMoon(?:/(\d+[\.\d]+))?', 'Pale Moon', 1),
			array('Camino(?:/(\d+[\.\d]+))?', 'Camino', 1),
			array('Chimera(?:/(\d+[\.\d]+))?', 'Camino', 1),
			array('Iceweasel(?:/(\d+[\.\d]+))?', 'Iceweasel', 1),
			array('IceDragon(?:/(\d+[\.\d]+))?', 'IceDragon', 1),
			array('YaBrowser(?:/(\d+[\.\d]+))?', 'Yandex', 1),
			array('SamsungBrowser(?:/(\d+[\.\d]+))?', 'Samsung Browser', 1),
			array('Galeon(?:/(\d+[\.\d]+))?', 'Galeon', 1),
			array('LG Browser(?:/(\d+[\.\d]+))', 'LG Browser', 1),
			array('Netscape(?:/(\d+[\.\d]+))?', 'Netscape', 1),
			array('Navigator(?:/(\d+[\.\d]+))?', 'Netscape', 1),
			array('Arora(?:/(\d+[\.\d]+))?', 'Arora', 1)
		);

		$found = false;
		foreach ($patterns as $pat) {
			$matches = $this->matchUserAgent($useragent, $pat[0]);
			if (!$matches) { continue; }
			$is_tv = $this->deviceIsTV($useragent);
			if ($is_tv) {
				$this->device['type'] = 'tv';
			} else {
				$this->device['type'] = 'desktop';
			}
			$this->device['name'] = $pat[1];
			$this->device['version'] = $this->versionFromMatches($pat[2], $matches);
			$found = true;
			break;
		}
		return $found;
	}


	private function checkBrowserMozilla($useragent) {
		if (stripos($useragent,'mozilla') === false) { return false; }
		$matches = $this->matchUserAgent($useragent, 'rv:(?:(\d+[\.\d]+))?', 1);
		$is_tv = $this->deviceIsTV($useragent);
		if ($is_tv) {
			$this->device['type'] = 'tv';
		} else if (preg_match('#android|mobile|tablet|iphone|ipad|smartphone#i', $useragent)) {
			$this->device['type'] = 'mobile';
		} else {
			$this->device['type'] = 'desktop';
		}
		$this->device['name'] = 'Mozilla';
		if ($matches) {
			if (isset($matches[1])) {
				$this->device['version'] = $matches[1];
			}
		}
		return true;
	}


	/********************/
	/* CHECK WINDOWS OS */
	/********************/
	private function checkOsWindows($useragent) {
		$patterns = array(
			//mobile/tablet
			array('Windows Phone (?:OS)?[ ]?(\d+[\.\d]+)', 'Windows Phone', 1),
			array('XBLWP7|Windows Phone', 'Windows Phone', ''),
			array('Windows CE(?: (\d+[\.\d]+))?', 'Windows CE', 1),
			array('(?:IEMobile|Windows Mobile)(?: (\d+[\.\d]+))?', 'Windows Mobile', 1), 
			array('Windows NT 6.2; ARM;', 'Windows RT', ''),
			array('Windows NT 6.3; ARM;', 'Windows RT', '8.1'),
			//desktop
			array('CYGWIN_NT-10.0|Windows NT 10.0|Windows 10', 'Windows', '10'),
			array('CYGWIN_NT-6.4|Windows NT 6.4|Windows 10', 'Windows', '10'),
			array('CYGWIN_NT-6.3|Windows NT 6.3|Windows 8.1', 'Windows', '8.1'),
			array('CYGWIN_NT-6.2|Windows NT 6.2|Windows 8', 'Windows', '8'),
			array('CYGWIN_NT-6.1|Windows NT 6.1|Windows 7', 'Windows', '7'),
			array('CYGWIN_NT-6.0|Windows NT 6.0|Windows Vista', 'Windows', 'Vista'),
			array('CYGWIN_NT-5.2|Windows NT 5.2|Windows Server 2003 / XP x64', 'Windows', 'Server 2003'),
			array('CYGWIN_NT-5.1|Windows NT 5.1|Windows XP', 'Windows', 'XP'),
			array('CYGWIN_NT-5.0|Windows NT 5.0|Windows 2000', 'Windows', '2000'),
			array('CYGWIN_NT-4.0|Windows NT 4.0|WinNT|Windows NT', 'Windows', 'NT'),
			array('CYGWIN_ME-4.90|Win 9x 4.90|Windows ME', 'Windows', 'ME'),
			array('CYGWIN_98-4.10|Win98|Windows 98', 'Windows', '98'),
			array('CYGWIN_95-4.0|Win32|Win95|Windows 95|Windows_95', 'Windows', '95'),
			array('Windows 3.1', 'Windows', '3.1'),
			array('Windows', 'Windows', '')
		);

		return $this->checkOs($useragent, $patterns);
	}


	/********************/
	/* CHECK ANDROID OS */
	/********************/
	private function checkOsAndroid($useragent) {
		$patterns = array(
			array('(?:(?:Orca-)?Android|Adr)[ /](?:[a-z]+ )?(\d+[\.\d]+)', 'Android', 1),
			array('Android|Silk-Accelerated=[a-z]{4,5}', 'Android', ''),
			array('BeyondPod|AntennaPod|Podkicker|DoggCatcher', 'Android', ''),
			array('(?:Ali)?YunOS[ /]?(\d+[\.\d]+)?', 'YunOS', 1),
			array('RazoDroiD(?: v(\d+[\.\d]*))?', 'RazoDroiD', 1),
			array('MildWild(?: CM-(\d+[\.\d]*))?', 'MildWild', 1),
			array('CyanogenMod(?:[\-/](?:CM)?(\d+[\.\d]*))?', 'CyanogenMod', 1),
			array('(?:.*_)?MocorDroid(?:(\d+[\.\d]*))?', 'MocorDroid', 1),
		);

		return $this->checkOs($useragent, $patterns);
	}


	/****************/
	/* CHECK MAC OS */
	/****************/
	private function checkOsMac($useragent) {
		$patterns = array(
			//https://user-agents.me/cfnetwork-version-list
			array('CFNetwork\/760', 'Mac', '10.11'),
			array('CFNetwork\/720', 'Mac', '10.10'),
			array('CFNetwork\/673', 'Mac', '10.9'),
			array('CFNetwork\/596', 'Mac', '10.8'),
			array('CFNetwork\/520', 'Mac', '10.7'),
			array('CFNetwork\/454', 'Mac', '10.6'),
			array('CFNetwork\/438|CFNetwork\/422|CFNetwork\/339|CFNetwork\/330|CFNetwork\/221|CFNetwork\/220|CFNetwork\/217', 'Mac', '10.5'),
			array('CFNetwork\/129|CFNetwork\/128', 'Mac', '10.4'),
			array('CFNetwork\/1\.2', 'Mac', '10.3'),
			array('CFNetwork\/1\.1', 'Mac', '10.2'),
			array('Mac OS X(?: (?:Version )?(\d+(?:[_\.]\d+)+))?', 'Mac', 1),
			array('Mac (\d+(?:[_\.]\d+)+)', 'Mac', 1),
			array('Darwin|Macintosh|Mac_PowerPC|PPC|Mac PowerPC|iMac|MacBook', 'Mac', '')
		);

		return $this->checkOs($useragent, $patterns);
	}


	/****************/
	/* CHECK IOS OS */
	/****************/
	private function checkOsIos($useragent) {
		$patterns = array(
			//https://user-agents.me/cfnetwork-version-list
			array('CFNetwork\/808|CFNetwork\/790', 'iOS', '10'),
			array('CFNetwork\/758\.4|CFNetwork\/758\.3', 'iOS', '9.3'),
			array('CFNetwork\/758\.2', 'iOS', '9.2'),
			array('CFNetwork\/758', 'iOS', '9'),
			array('CFNetwork\/711', 'iOS', '8'),
			array('CFNetwork\/672', 'iOS', '7'),
			array('CFNetwork\/609|CFNetwork\/602', 'iOS', '6'),
			array('CFNetwork\/548', 'iOS', '5'),
			array('CFNetwork\/485', 'iOS', '4'),
			array('CFNetwork\/467', 'iOS', '3.2'),
			array('CFNetwork\/459', 'iOS', '3.1'),
			array('(?:CPU OS|iPh(?:one)?[ _]OS|iOS)[ _/](\d+(?:[_\.]\d+)*)', 'iOS', 1),
			array('(?:Apple-)?(?:iPhone|iPad|iPod)(?:.*Mac OS X.*Version/(\d+\.\d+)|; Opera)?', 'iOS', 1),
			array('Podcasts/(?:[\d\.]+)|Instacast(?:HD)?/(?:\d\.[\d\.abc]+)|Pocket Casts, iOS|Overcast|Castro|Podcat|i[cC]atcher', 'iOS', ''),
			array('iTunes-(iPod|iPad|iPhone)/(?:[\d\.]+)', 'iOS', '')
		);

		return $this->checkOs($useragent, $patterns);
	}

	/***********************/
	/* CHECK LINUX/UNIX OS */
	/***********************/
	private function checkOsLinux($useragent) {
		$pattern = 'Debian|Knoppix|Mint|Ubuntu|Kubuntu|Xubuntu|Lubuntu|Fedora|Red\sHat|Mandriva|Gentoo|Sabayon|Slackware|SUSE|CentOS|BackTrack';
		$matches = $this->matchUserAgent($useragent, $pattern);
		if ($matches) {
			$this->device['os_name'] = $matches[0];
			$this->device['os_version'] = '';
			return true;
		}

		$patterns = array(
			array('SunOS|Solaris', 'Solaris', ''),
			array('FreeBSD(?:[/ ]?(\d+[\.\d]+))?', 'FreeBSD', 1),
			array('OpenBSD(?:[/ ]?(\d+[\.\d]+))?', 'OpenBSD', 1),
			array('NetBSD(?:[/ ]?(\d+[\.\d]+))?', 'NetBSD', 1),
			array('DragonFly(?:[/ ]?(\d+[\.\d]+))?', 'DragonFly', 1),
			array('Linux(?:OS)?[^a-z]', 'Linux', '')
		);

		return $this->checkOs($useragent, $patterns);
	}


	/****************/
	/* CHECK MAC OS */
	/****************/
	private function checkOsBlackBerry($useragent) {
		$patterns = array(
			array('(?:BB10;.+Version|Black[Bb]erry[0-9a-z]+|Black[Bb]erry.+Version)/(\d+[\.\d]+)', 'BlackBerry OS', 1),
			array('RIM Tablet OS (\d+[\.\d]+)', 'BlackBerry Tablet OS', 1),
			array('RIM Tablet OS|QNX|Play[Bb]ook', 'BlackBerry Tablet OS', ''),
			array('BlackBerry', 'BlackBerry OS', '')
		);
		return $this->checkOs($useragent, $patterns);
	}


	/*********************************/
	/* CHECK OTHER OPERATING SYSTEMS */
	/*********************************/
	private function checkOsOther($useragent) {
		$patterns = array(
			array('(?:Mobile|Tablet);.+Firefox/\d+\.\d+', 'Firefox OS', ''),
			array('CrOS [a-z0-9_]+ (\d+[\.\d]+)', 'Chrome OS', 1),
			array('(?:webOS|Palm webOS)(?:/(\d+[\.\d]+))?', 'webOS', 1),
			array('(?:PalmOS|Palm OS)(?:[/ ](\d+[\.\d]+))?|Palm', 'palmOS', 1),
			array('Xiino(?:.*v\. (\d+[\.\d]+))?', 'palmOS', 1),
			array('MorphOS(?:[ /](\d+[\.\d]+))?', 'MorphOS', 1),
			array('Symbian|SymbOS', 'Symbian OS', ''),
			array('GoogleTV(?:[ /](\d+[\.\d]+))?', 'Google TV', 1),
			array('AppleTV(?:/?(\d+[\.\d]+))?', 'Apple TV', 1),
			array('WebTV/(\d+[\.\d]+)', 'WebTV', 1),
			array('AmigaOS|AmigaVoyager|Amiga-AWeb', 'AmigaOS', ''),
			array('Nintendo Wii', 'Nintendo', 'Wii'),
			array('PlayStation', 'PlayStation', ''),
			array('Xbox|KIN\.(?:One|Two)', 'Xbox', '360'),
			array('OS\/2', 'OS/2', ''),
			array('Sailfish|Jolla', 'Sailfish OS', ''),
			array('Tizen', 'Tizen', '')
		);
		return $this->checkOs($useragent, $patterns);
	}


	/**************************/
	/* CHECK OPERATING SYSTEM */
	/**************************/
	private function checkOs($useragent, $patterns) {
		if (!$patterns) { return false; }
		$found = false;
		foreach ($patterns as $pat) {
			$matches = $this->matchUserAgent($useragent, $pat[0]);
			if (!$matches) { continue; }
			$this->device['os_name'] = $pat[1];
			$this->device['os_version'] = $this->versionFromMatches($pat[2], $matches);
			$found = true;
			break;
		}
		return $found;
	}


	/*******************************************************/
	/* CHECK ALL OPERATING SYSTEMS WITH OPTIONAL EXCEPTION */
	/*******************************************************/
	private function checkAllOperatingSystems($useragent, $os_exception='') {
		$oses = array('Windows', 'Linux', 'Mac', 'Android', 'Ios', 'BlackBerry', 'Other');
		$found = false;
		foreach ($oses as $os) {
			if ($os == $os_exception) { continue; }
			$method = 'checkOs'.$os;
			$found = $this->$method($useragent);
			if ($found) { break; }
		}
		return $found;
	}


	/****************************/
	/* GET VERSION FROM MATCHES */
	/****************************/
	private function versionFromMatches($pattern_idx, $matches) {
		$version = '';
		if ($pattern_idx != '') {
			if (is_int($pattern_idx)) {
				$version = isset($matches[$pattern_idx]) ? $matches[$pattern_idx] : '';
				if (preg_match('/^[0-9\.]+$/', $version)) {
					$parts = explode('.', $version);
					$version = $parts[0];
					if (isset($parts[1])) { $version .= '.'.$parts[1]; }
				}
			} else {
				$version = $pattern_idx;
			}
			if (strlen($version) > 10) { $version = ''; }//wrong version => reset it
		}
		return $version;
	}


	/***********************************/
	/* CHECK IF A DEVICE IS A SMART TV */
	/***********************************/
	private function deviceIsTV($useragent) {
		$tv = false;
		$patterns = array('GoogleTV', 'SmartTV', 'Smart TV', 'SMART-TV', 'Android TV', 'GTV100', 'HbbTV', 'POV_TV-HDMI', '(TV;', '+SCREEN', 'Changhong', 'NETTV', 'AND1E', 'TSBNetTV');
		foreach ($patterns as $pat) {
			if (stripos($useragent, $pat) !== false) { $tv = true; break; }
		}
		return $tv;
	}


	/***************************************************/
	/* INDEPENDENT MOBILE CHECK (FASTER, MORE UPDATED) */
	/* Harald Hope, 5.4.12, http://techpatterns.com    */
	/***************************************************/
	public function isMobile($useragent='', $forcescan=false) {
		if ($useragent == '') {
			if (!isset($_SERVER['HTTP_USER_AGENT'])) { return false; }
			$useragent = $_SERVER['HTTP_USER_AGENT'];
		}
		if ($useragent == '') { return false; }

		if (!$forcescan) {
			if ($this->useragent == $useragent) {//fully analized previously
				return ($this->device['type'] == 'mobile') ? true : false;
			}			
		}

		if ($this->deviceIsTV($useragent)) { return false; }

		$pat1 = 'android|blackberry|linux\sarmv|palmos|palmsource|windows\sce|windows\sphone|iemobile|ddipocket|ipad|ipod|iphone|playstation|nintendo|samsung|sharp|zune|vodafone|smartphone|mobile|tablet|opera\smobi|opera\smini|fennec';
		$pat2 = 'HTC|NexusHD2|StarShine|StarTrail|STARADDICT|StarText|StarNaute|StarXtrem|StarTab|Nokia|Lumia|Maemo\sRX|portalmmm|Symbian|BB10;|PlayBook|Xiino|ViewSonic|ViewPad|ViewPhone|NetCast|SAGEM|j-phone|skyfire';
		$pat3 = 'Kindle|Huawei|EasyPad|EasyPhone|Elephone|TouchPad|webOS|hp-tablet|MicroMax|Alcatel|Xiaomi|Wexler|Walton|Walpad|Vodafone|ZTE|AxonPhone|Xenta|Smartfren|Androtab|Andromax|QMobile|QTab|Q-Smart|MobilePhone';
		$pat4 = 'Ericsson|Xperia|PadFone|BENQ|Bmobile|Turbo-X|SmartPad|Lenovo|IdeaTab|IdeaPad|Thinkpad|Yoga\sTablet|SlidePad|Garminfone|Capitel|Coolpad|Hasee|Hisense|Koobee|Kyocera|blazer|avantgo|reqwirelessweb|netfront';

		if (preg_match('#'.$pat1.'#i', $useragent, $matches)) { return true; }
		if (preg_match('#'.$pat2.'#i', $useragent, $matches)) { return true; }
		if (preg_match('#'.$pat3.'#i', $useragent, $matches)) { return true; }
		if (preg_match('#'.$pat4.'#i', $useragent, $matches)) { return true; }

		return false;
	}


	/**********************************/
	/* PATTERNS USED TO DETECT ROBOTS */
	/**********************************/
	private function getRobotPatterns() {
		$patterns = array(
			array('360Spider(-Image|-Video)?', '360Spider'),
			array('Aboundex', 'Aboundexbot'),
			array('AcoonBot', 'Acoon'),
			array('AddThis\.com', 'AddThis.com'),
			array('AhrefsBot', 'aHrefs Bot'),
			array('ia_archiver|alexabot|verifybot', 'Alexa Crawler'),
			array('AmorankSpider', 'Amorank Spider'),
			array('Applebot', 'Applebot'),
			array('Curious George', 'Analytics SEO Crawler'),
			array('archive\.org_bot|special_archiver', 'archive.org bot'),
			array('Ask Jeeves\/Teoma', 'Ask Jeeves'),
			array('Backlink-Ceck\.de', 'Backlink-Ceck.de'),
			array('BacklinkCrawler', 'BacklinkCrawler'),
			array('baiduspider(-image)?|baidu Transcoder|baidu.*spider', 'Baidu Spider'),
			array('BazQux', 'BazQux Reader'),
			array('MSNBot|msrbot|bingbot|BingPreview|msnbot-(UDiscovery|NewsBlogs)|adidxbot', 'BingBot'),
			array('Blekkobot', 'Blekkobot'),
			array('BLEXBot(Test)?', 'BLEXBot Crawler'),
			array('Bloglovin', 'Bloglovin'),
			array('Blogtrottr', 'Blogtrottr'),
			array('BountiiBot', 'Bountii Bot'),
			array('Browsershots', 'Browsershots'),
			array('BUbiNG', 'BUbiNG'),
			array('(?<!HTC)[ _]Butterfly\/', 'Butterfly Robot'),
			array('CareerBot', 'CareerBot'),
			array('CCBot', 'ccBot crawler'),
			array('Cliqzbot', 'Cliqzbot'),
			array('CloudFlare-AlwaysOnline', 'CloudFlare Always Online'),
			array('coccoc\/', 'Cốc Cốc Bot'),
			array('CommaFeed', 'CommaFeed'),
			array('Dataprovider', 'Dataprovider'),
			array('Daum(oa)?[ /][0-9]', 'Daum'),
			array('Dazoobot', 'Dazoobot'),
			array('discobot(-news)?', 'Discobot'),
			array('Domain Re-Animator Bot|support@domainreanimator.com', 'Domain Re-Animator Bot'),
			array('DotBot', 'DotBot'),
			array('EasouSpider', 'Easou Spider'),
			array('EMail Exractor', 'EMail Exractor'),
			array('Exabot(-Thumbnails|-Images)?|ExaleadCloudview', 'ExaBot'),
			array('ExactSeek Crawler', 'ExactSeek Crawler'),
			array('Ezooms', 'Ezooms'),
			array('facebookexternalhit|facebookplatform', 'Facebook External Hit'),
			array('Feedbin', 'Feedbin'),
			array('FeedBurner', 'FeedBurner'),
			array('Feed Wrangler', 'Feed Wrangler'),
			array('(Meta)?Feedly(Bot|App)?', 'Feedly'),
			array('Feedspot', 'Feedspot'),
			array('Fever\/[0-9]', 'Fever'),
			array('Genieo', 'Genieo Web filter'),
			array('Gluten\sFree\sCrawler', 'Gluten Free Crawler'),
			array('ichiro\/mobile goo', 'Goo'),
			array('Google Page Speed Insights', 'Google PageSpeed Insights'),
			array('google_partner_monitoring', 'Google Partner Monitoring'),
			array('via ggpht\.com GoogleImageProxy', 'Gmail Image Proxy'),
			array('SentiBot', 'SentiBot'),//before Googlebot
			array('Googlebot(-Mobile|-Image|-Video|-News)?|Feedfetcher-Google|Google-Test|Google-Site-Verification|Google\sWeb\sPreview|AdsBot-Google(-Mobile)?|Mediapartners-Google|Google.*/\+/web/snippet|GoogleProducer|Google[ -]Publisher[ -]Plugin', 'Googlebot'),
			array('heritrix', 'Heritrix'),
			array('HTTPMon', 'HTTPMon'),
			array('ICC-Crawler', 'ICC-Crawler'),
			array('iisbot', 'IIS Site Analysis'),
			array('kouio', 'Kouio'),
			array('larbin', 'Larbin web crawler'),
			//array('linkdexbot(-mobile)?|linkdex\.com', 'Linkdex Bot'),
			array('linkdexbot', 'Linkdex Bot'),
			array('LinkedInBot', 'LinkedIn Bot'),
			array('ltx71', 'LTX71'),
			array('Mail\.RU(_Bot)?', 'Mail.Ru Bot'),
			array('magpie-crawler', 'Magpie-Crawler'),
			array('MagpieRSS', 'MagpieRSS'),
			array('meanpathbot', 'Meanpath Bot'),
			array('MetaJobBot', 'MetaJobBot'),
			array('MixrankBot', 'Mixrank Bot'),
			array('MJ12bot', 'MJ12 Bot'),
			array('MojeekBot', 'MojeekBot'),
			array('NalezenCzBot', 'NalezenCzBot'),
			array('Netcraft (Web Server Survey|SSL Server Survey)', 'Netcraft Survey Bot'),
			array('Netvibes', 'Netvibes'),
			array('NewsBlur .*(Fetcher|Finder)', 'NewsBlur'),
			array('NewsGatorOnline', 'NewsGator'),
			array('nlcrawler', 'NLCrawler'),
			array('omgilibot', 'Omgili bot'),
			array('OpenindexSpider', 'Openindex Spider'),
			array('spbot', 'OpenLinkProfiler'),
			array('OpenWebSpider', 'OpenWebSpider'),
			array('OrangeBot|VoilaBot', 'Orange Bot'),
			array('PaperLiBot', 'PaperLiBot'),
			array('phpservermon', 'PHP Server Monitor'),
			array('psbot(-page)?', 'Picsearch bot'),
			array('Pingdom\.com', 'Pingdom Bot'),
			array('QuerySeekerSpider', 'QuerySeekerSpider'),
			array('Qwantify', 'Qwantify'),
			array('Rainmeter', 'Rainmeter'),
			array('redditbot', 'Reddit Bot'),
			array('rogerbot', 'Rogerbot'),
			array('ROI Hunter', 'ROI Hunter'),
			array('SafeDNSBot', 'SafeDNSBot'),
			array('Scrapy', 'Scrapy'),
			array('Screaming Frog SEO Spider', 'Screaming Frog SEO Spider'),
			array('ScreenerBot', 'ScreenerBot'),
			array('SemrushBot', 'Semrush Bot'),
			array('SensikaBot', 'Sensika Bot'),
			array('SEOENG(World)?Bot', 'SEOENGBot'),
			array('SkypeUriPreview', 'Skype URI Preview'),
			array('SeznamBot|SklikBot|Seznam screenshot-generator', 'Seznam Bot'),
			array('ShopWiki', 'ShopWiki'),
			array('SilverReader', 'SilverReader'),
			array('SimplePie', 'SimplePie'),
			array('SISTRIX Crawler', 'SISTRIX Crawler'),
			array('sixy.ch', 'Sixy.ch'),
			array('Slackbot|Slack-ImgProxy', 'Slackbot'),
			array('(Sogou (web|inst|Pic) spider)|New-Sogou-Spider', 'Sogou Spider'),
			array('Sosospider|Sosoimagespider', 'Soso Spider'),
			array('Superfeedr bot', 'Superfeedr Bot'),
			array('Spinn3r', 'Spinn3r'),
			array('Sputnik(Image)?Bot', 'Sputnik Bot'),
			array('SurveyBot', 'Survey Bot'),
			array('TelegramBot', 'TelgramBot'),
			array('TinEye-bot', 'TinEye Crawler'),
			array('Tiny Tiny RSS', 'Tiny Tiny RSS'),
			array('TurnitinBot', 'TurnitinBot'),
			array('TweetedTimes Bot', 'TweetedTimes Bot'),
			array('TweetmemeBot', 'Tweetmeme Bot'),
			array('Twitterbot', 'Twitterbot'),
			array('UptimeRobot', 'Uptime Robot'),
			array('URLAppendBot', 'URLAppendBot'),
			array('VSMCrawler', 'Visual Site Mapper Crawler'),
			array('Jigsaw', 'W3C CSS Validator'),
			array('W3C_I18n-Checker', 'W3C I18N Checker'),
			array('W3C-checklink', 'W3C Link Checker'),
			array('W3C_Validator', 'W3C Markup Validation Service'),
			array('W3C-mobileOK', 'W3C MobileOK Checker'),
			array('W3C_Unicorn', 'W3C Unified Validator'),
			array('WeSEE(:Search)?', 'WeSEE:Search'),
			array('WebbCrawler', 'WebbCrawler'),
			array('websitepulse[+ ]checker', 'WebSitePulse'),
			array('Wotbox', 'Wotbox'),
			array('yacybot', 'YaCy'),
			array('Yahoo! Slurp|Yahoo!-AdCrawler', 'Yahoo! Slurp'),
			array('Yahoo Link Preview|Yahoo:LinkExpander:Slingstone', 'Yahoo! Link Preview'),
			array('YahooCacheSystem', 'Yahoo! Cache System'),
			array('Yandex(SpravBot|ScreenshotBot|MobileBot|AccessibilityBot|ForDomain|Vertis|Market|Catalog|Calendar|Sitelinks|AdNet|Pagechecker|Webmaster|Media|Video|Bot|Images|Antivirus|Direct|Blogs|Favicons|ImageResizer|News(links)?|Metrika|\.Gazeta Bot)|YaDirectFetcher', 'Yandex Bot'),
			array('Yeti', 'Yeti/Naverbot'),
			array('YoudaoBot', 'Youdao Bot'),
			array('YOURLS v[0-9]', 'Yourls'),
			array('YRSpider|YYSpider', 'Yunyun Bot'),
			array('Zookabot', 'Zookabot'),
			array('ZumBot', 'ZumBot'),
			array('YottaaMonitor', 'Yottaa Site Monitor'),
			array('Yahoo Ad monitoring.*yahoo-ad-monitoring-SLN24857.*', 'Yahoo Gemini'),
			array('.*Java.*outbrain', 'Outbrain'),
			array('HubPages.*crawlingpolicy', 'HubPages'),
			array('Pinterest\/\d\.\d.*www\.pinterest\.com.*', 'Pinterest'),
			array('Site24x7', 'Site24x7 Website Monitoring'),
			array('www\.monitor\.us', 'Monitor.Us'),
			array('Catchpoint( bot)?', 'Catchpoint'),
			array('Zao\/', 'Zao'),
			array('lycos', 'Lycos'),
			array('Slurp', 'Inktomi Slurp'),
			array('Speedy Spider', 'Speedy'),
			array('ScoutJet', 'ScoutJet'),
			array('nrsbot|netresearch', 'NetResearchServer'),
			array('scooter', 'Scooter'),
			array('gigabot', 'Gigabot'),
			array('CatchBot', 'CatchBot'),
			array('charlotte', 'Charlotte'),
			array('Pompos', 'Pompos'),
			array('ichiro', 'ichiro'),
			array('PagePeeker', 'PagePeeker'),
			array('WebThumbnail', 'WebThumbnail'),
			array('Willow Internet Crawler', 'Willow Internet Crawler'),
			array('EmailWolf', 'EmailWolf'),
			array('NetLyzer FastProbe', 'NetLyzer FastProbe'),
			array('AdMantX.*admantx\.com', 'ADMantX'),
			array('Server Density Service Monitoring.*', 'Server Density'),
			array('(A6-Indexer|nuhk|TsolCrawler|Yammybot|Openbot|Gulper Web Bot|grub-client|Download Demon|SearchExpress|Microsoft URL Control|borg|altavista|teoma|blitzbot|oegp|furlbot|http%20client|polybot|htdig|mogimogi|larbin|scrubby|searchsight|seekbot|semanticdiscovery|snappy|vortex(?! Build)|zeal|fast-webcrawler|converacrawler|dataparksearch|findlinks|BrowserMob|HttpMonitor|ThumbShotsBot|URL2PNG|ZooShot|GomezA|Google SketchUp|Read%20Later|Minimo|RackspaceBot)', 'Generic Bot'),
			array('Nutch', 'Nutch-based Bot'),
			array('YisouSpider', 'Yisou Spider'),
			array('Bumble\sBee|WikiApiary', 'Bumble Bee'),
			array('yoozBot|vebidoobot|DomainStatsBot|RSSingBot|CISPA\sVulnerability', 'Generic Bot'),
			array('[a-z0-9\-_]*((?<!cu)bot|crawler|archiver|transcoder|spider)([^a-z]|$)', 'Generic Bot')
		);

		return $patterns;
	}


	/************************************************/
	/* PATTERNS USED TO DETECT RSS/ATOM AGGREGATORS */
	/************************************************/
	private function getFeedPatterns() {
		$patterns = array(
			array('AideRSS', 'AideRSS'),
			array('Akregator', 'Akregator'),
			array('Apple-PubSub', 'Apple PubSub'),
			array('AppleSyndication', 'Safari RSS reader'),
			array('BlogBridge', 'BlogBridge'),
			array('Bloglines', 'Bloglines'),
			array('BashPodder', 'BashPodder'),
			array('GreatNews', 'GreatNews'),
			array('NetNewsWire', 'NetNewsWire'),
			array('NewsFire', 'NewsFire'),
			array('NewzCrawler', 'NewzCrawler'),
			array('ReadKit', 'ReadKit'),
			array('RSSOwl', 'RSSOwl'),
			array('RSSBandit', 'RSS Bandit'),
			array('RSS\sJunkie', 'RSS Junkie'),
			array('RSS\sMenu', 'RSS Menu'),
			array('NewsBlur', 'NewsBlur'),
			array('NewsFox', 'NewsFox'),
			array('Feeddler', 'Feeddler'),
			array('Downcast', 'Downcast'),
			array('FeedDemon', 'FeedDemon'),
			array('Feedfetcher', 'Feedfetcher'),
			array('Liferea', 'Liferea'),
			array('JetBrains\sOmea\sReader', 'JetBrains Omea Reader'),
			array('Netvibes', 'Netvibes feed reader'),
			array('Windows-RSS-Platform', 'IE RSS reader'),
			array('YahooFeedSeeker', 'Yahoo Feed Seeker')
		);

		return $patterns;
	}

}

?>