<?php
/* $Id$ */
namespace Mammut\Info;

(defined('MAMMUT') && (basename(__FILE__) != basename($_SERVER['PHP_SELF']))) or die('ACCESS DENIED');

/**
 * Information about the currently used browser
 * 
 * @author Stefan Daurer <s.daurer@q-spot.org>
 * @package Mammut\Info
 */
class UserAgent extends \Mammut\StrictObject {
	const _VERSION_ = '0.5.0.1';
	
	// stores the active agent
	private $agent = '';
	private $bot = false;
	private $smallscreen = false;
	private $mobile = false;
	private $console = false;
	
	// these are known bot regex for isBot and getBotService, remember to escape # cause it's the regex limiter
	private static $bots = array(
			'200please' => '200PleaseBot','ahrefs' => 'AhrefsBot',
			'almaden' => 'www.almaden.ibm.com/cs/crawler','arachnoidea' => 'Arachnoidea',
			'architext' => 'ArchitextSpider','bing' => 'bingbot','blitz' => 'B-l-i-t-z-Bot',
			'baidu' => 'Baiduspider','become' => 'BecomeBot','cazoodle' => 'CazoodleBot',
			'careerbot' => 'CareerBot','cfetch' => 'cfetch','convera' => 'ConveraCrawler',
			'domcraw' => 'DomainCrawler','entireweb' => 'Speedy.*Spider.*www.entireweb.com',
			'exabot' => 'Exabot','extractor' => 'ExtractorPro','ezooms' => 'Ezooms',
			'fast' => 'FAST-WebCrawler','fdse' => 'FDSE robot','fido' => 'fido',
			'fireball' => 'KIT-Fireball','flatland' => 'flatlandbot','gais' => 'Gaisbot',
			'gecko' => 'geckobot','giga' => 'Gigabot','girafa' => 'Girafabot',
			'google' => '(Googlebot|Feedfetcher-Google|Mediapartners-Google|Google-Sitemaps|google.bot)',
			'grub' => 'grub-client','gulliver' => 'Gulliver','httrack' => 'HTTrack',
			'ia' => 'ia_archiver','icjobs' => 'iCcrawler','iask' => 'iaskspider','is' => 'InfoSeek',
			'jeeves' => 'Ask Jeeves','jobsde' => 'jobs\.de-Robot','jyxobot' => 'Jyxobot',
			'kinja' => 'kinjabot','larbin' => 'larbin','leia' => 'LEIA','lm' => 'lmspider',
			'lycos' => 'Lycos_Spider','mail.ru' => 'Mail.RU_Bot','mf' => 'MuscatFerret',
			'msn' => 'msnbot','majestic12' => 'MJ12bot','mlbot' => 'MLBot','naver' => 'NaverBot',
			'neofonie' => 'neofonie search','netluchs' => 'Netluchs','ocelli' => 'Ocelli',
			'omniexpl' => 'OmniExplorer_Bot','pagesinv' => 'PagesInventory','picsearch' => '^psbot/',
			'pixray' => 'Pixray-Seeker','plone' => 'Plonebot','poly' => 'polybot',
			'pompos' => 'Pompos','scooter' => 'Scooter','scoutjet' => 'ScoutJet',
			'seekport' => 'Seekbot','sistrix' => 'SISTRIX Crawler','sitexp' => 'SiteExplorer',
			'slurp' => 'Slurp','snap' => 'Snapbot','snoopy' => 'Snoopy','su' => 'TheSuBot',
			'suchen' => 'http://www.suchen.de/faq.html','survey' => 'SurveyBot',
			'thumbshot' => 'thumbshots-de-bot','touristmap' => 'TouristMap',
			'turnitin' => 'TurnitinBot','twiceler' => 'Twiceler','teoma' => 'Teoma',
			'ultraseek' => 'Ultraseek','viola' => 'ViolaBot','webbandit' => 'webbandit',
			'yahoo' => 'Yahoo','yandex' => 'YandexBot','yeti' => 'Yeti/',
			'yanga' => 'Yanga\s*WorldSearch','zyborg' => 'ZyBorg',
			'-unkn-' => '^libwww-perl/[0-9.]*$');
	private static $mobileUA = array(
			'opera\s*mini','Windows\s*Phone','Windows\s*CE','Win\s*CE','Windows\s*Mobile',
			'Win\s*Mobile','Symbian','Android','Palm');
	private static $consoleUA = array('Lynx','Links','w3m');

	/**
	 * creates a new browser object based on a user agent string.
	 * if no string is given the user agent of the current session is used
	 *
	 * @param $agent string
	 *        	an optional user agent string
	 */
	public function __construct($agent = false) {
		if($agent !== false)
			$this->agent = $agent;
		else
			$this->agent = $_SERVER['HTTP_USER_AGENT'];
			// cleanup messy user agent strings
		if(strpos($this->agent, 'User-Agent:') === 0) {
			$this->agent = substr($this->agent, strlen('User-Agent:'));
		}
		$this->agent = trim($this->agent);
		$regex = '#(' . implode('|', self::$bots) . ')#i';
		$this->bot = (bool) (preg_match($regex, $this->agent) != 0);
		if(!$this->bot) {
			$regex = '#(' . implode('|', self::$mobileUA) . ')#i';
			$this->mobile = (bool) (preg_match($regex, $this->agent) != 0);
			if($this->mobile) {
				$smallUA = array('Windows\s*CE','Win\s*CE');
				$regex = '#(' . implode('|', $smallUA) . ')#i';
				$this->smallscreen = (bool) (preg_match($regex, $this->agent) != 0);
			}
		}
		if(!($this->bot || $this->mobile)) {
			$regex = '#(' . implode('|', self::$consoleUA) . ')#i';
			$this->console = (bool) (preg_match($regex, $this->agent) != 0);
		}
	}

	public function getAgentString() {
		return $this->agent;
	}

	/**
	 * parses the agent string for browser information
	 *
	 * @return array an array that contain the browser information
	 */
	public function getBrowserInfo() {
		$info = array();
		$info['name'] = 'unkown';
		$info['extra'] = false;
		$info['version'] = '0.0';
		$info['major'] = '?';
		$info['minor'] = '?';
		
		$info['f_image'] = false; // array of supported image types
		$info['f_css'] = false; // css support/version number
		$info['f_js'] = false; // javascript support/version number
		$info['f_svg'] = false; // svg support
		$info['f_utf8'] = false; // utf8 support
		$info['f_wf2'] = false; // webforms 2
		

		if(preg_match('#Links\\s*\\(([0-9]+)\.([0-9]+).*#i', $this->agent, $match)) {
			$info['name'] = 'links';
			$info['major'] = (int) $match[1];
			$info['minor'] = $match[2];
			$info['version'] = "{$info['major']}.{$info['minor']}";
			return $info;
		}
		if(preg_match('#Lynx/([0-9]+)\.([0-9]+).*#i', $this->agent, $match)) {
			$info['name'] = 'lynx';
			$info['major'] = (int) $match[1];
			$info['minor'] = $match[2];
			$info['version'] = "{$info['major']}.{$info['minor']}";
			return $info;
		}
		if(preg_match('#w3m/([0-9]+)\.([0-9]+).*#i', $this->agent, $match)) {
			$info['name'] = 'w3m';
			$info['major'] = (int) $match[1];
			$info['minor'] = $match[2];
			$info['version'] = "{$info['major']}.{$info['minor']}";
			return $info;
		}
		if(preg_match('#MSIE\s*([1-9][0-9]*)\.([[0-9]+).*AOL\s*([0-9]+)\.([0-9]+)#i', $this->agent, $match)) {
			$info['name'] = 'aol';
			$info['major'] = (int) $match[3];
			$info['minor'] = $match[4];
			$info['version'] = "{$info['major']}.{$info['minor']}";
			
			$info['f_image'] = array('gif','jpeg','bmp');
			
			if($match[1] >= 5) {
				$info['f_css'] = 1;
				$info['f_js'] = 1.0;
			}
			if($match[1] >= 6) {
				$info['f_js'] = 1.0;
			}
			if($match[1] >= 7) {
				$info['f_image'] = array('gif','jpeg','png','png-a');
				$info['f_css'] = 2;
				$info['f_utf8'] = true;
			}
			if($match[1] >= 8) {
				$info['f_png_a'] = true;
				$info['f_css'] = 2;
				$info['f_utf8'] = true;
			}
		}
		if(preg_match('#MSIE.*Opera#i', $this->agent, $match)) {
			$info['name'] = 'opera';
			$info['extra'] = 'as_ie';
		}
		elseif(preg_match('#Firefox.*Opera#i', $this->agent, $match)) {
			$info['name'] = 'opera';
			$info['extra'] = 'as_ff';
		}
		elseif(preg_match('#MSIE\s*([1-9][0-9]*)\.([0-9]+)#i', $this->agent, $match)) {
			$info['name'] = 'iexplorer';
			$info['major'] = (int) $match[1];
			$info['minor'] = $match[2];
			$info['version'] = "{$info['major']}.{$info['minor']}";
			
			$info['f_image'] = array('gif','jpeg','bmp');
			
			if($info['major'] >= 5) {
				$info['f_css'] = 1;
				$info['f_js'] = 1.0;
			}
			if($info['major'] >= 6) {
				$info['f_js'] = 1.0;
				$info['f_image'] = array('gif','jpeg','png','bmp');
			}
			if($info['major'] >= 7) {
				$info['f_image'] = array('gif','jpeg','png','png-a','bmp');
				$info['f_css'] = 2;
				$info['f_utf8'] = true;
			}
			if($info['major'] >= 8) {
				$info['f_css'] = 2;
				$info['f_utf8'] = true;
			}
			if($info['major'] >= 9) {
				$info['f_css'] = 2;
				$info['f_utf8'] = true;
			}
			if($info['major'] >= 10) {
				$info['f_css'] = 3;
				$info['f_utf8'] = true;
			}
		}
		elseif(preg_match('#SeaMonkey/([0-9]*)\.([0-9]+)#i', $this->agent, $match)) {
			$info['name'] = 'seamonkey';
			$info['major'] = (int) $match[1];
			$info['minor'] = $match[2];
			$info['version'] = "{$info['major']}.{$info['minor']}";
			$info['f_image'] = array('gif','jpeg','png','bmp');
		}
		elseif(preg_match('#Netscape[\s/]*([0-9]*)\.([0-9]+)#i', $this->agent, $match)) {
			$info['name'] = 'opera';
			$info['major'] = (int) $match[1];
			$info['minor'] = $match[2];
			$info['version'] = "{$info['major']}.{$info['minor']}";
			$info['f_image'] = array('gif','jpeg','bmp');
		}
		elseif(preg_match('#Mozilla.*(Firefox|Iceweasel)/([0-9]+)\.([0-9]+)#i', $this->agent, $match)) {
			$info['name'] = (strtolower($match[1]) == 'firefox') ? 'firefox' : 'iceweasel';
			$info['major'] = (int) $match[2];
			$info['minor'] = $match[3];
			$info['version'] = "{$info['major']}.{$info['minor']}";
			
			$info['f_image'] = array('gif','jpeg','png','bmp');
			
			if($info['major'] >= 2) {
				$info['f_css'] = 2;
				$info['f_js'] = 1.0;
				$info['f_utf8'] = true;
				$info['f_image'] = array('gif','jpeg','png','png-a','bmp');
			}
			if($info['major'] >= 3) {
				$info['f_css'] = 2;
				$info['f_js'] = 1.0;
				$info['f_utf8'] = true;
			}
			if($info['major'] >= 4) {
				$info['f_css'] = 2;
				$info['f_js'] = 1.0;
				$info['f_utf8'] = true;
			}
		}
		elseif(preg_match('#Konqueror/([0-9]+)\.([0-9]+)#i', $this->agent, $match)) {
			$info['name'] = 'konqueror';
			$info['major'] = (int) $match[1];
			$info['minor'] = (int) $match[2];
			$info['version'] = "{$info['major']}.{$info['minor']}";
			
			$info['f_css'] = 2;
			$info['f_js'] = 1.0;
			$info['f_utf8'] = true;
			$info['f_image'] = array('gif','jpeg','png','png-a','bmp');
		}
		elseif(preg_match('#Chrome/([0-9]+)\.([0-9]+)#i', $this->agent, $match)) {
			$info['name'] = 'chrome';
			$info['major'] = (int) $match[1];
			$info['minor'] = $match[2];
			$info['version'] = "{$info['major']}.{$info['minor']}";
			
			$info['f_css'] = 2;
			$info['f_js'] = 1.0;
			$info['f_utf8'] = true;
			$info['f_image'] = array('gif','jpeg','png','png-a','bmp');
		}
		elseif(preg_match('#iPhone.*Safari#i', $this->agent, $match)) {
			$info['name'] = 'safari-iphone-mobile';
			$info['major'] = '?';
			$info['minor'] = '?';
			$info['version'] = "{$info['major']}.{$info['minor']}";
			
			$info['f_image'] = array('gif','jpeg','png','png-a','bmp');
		}
		elseif(preg_match('#Safari#i', $this->agent, $match)) {
			$info['name'] = 'safari';
			$info['major'] = '?';
			$info['minor'] = '?';
			$info['version'] = "{$info['major']}.{$info['minor']}";
			
			$info['f_image'] = array('gif','jpeg','png','bmp');
		}
		elseif(preg_match('#Opera[\s/]*([1-9][0-9]*)\.([0-9]+)#i', $this->agent, $match)) {
			$info['name'] = 'opera';
			$info['major'] = (int) $match[1];
			$info['minor'] = $match[2];
			$info['version'] = "{$info['major']}.{$info['minor']}";
			$info['f_image'] = array('gif','jpeg','png','bmp');
			
			if($info['major'] >= 7) {
				$info['f_css'] = 1;
				$info['f_js'] = 1.0;
			}
			if($info['major'] >= 8) {
				$info['f_css'] = 2;
				$info['f_js'] = 1.0;
				$info['f_utf8'] = true;
			}
			if($info['major'] >= 9) {
				$info['f_image'] = array('gif','jpeg','png','png-a','bmp');
			}
		}
		if($info['name'] == 'opera' && preg_match('#.*Version/([1-9][0-9]*)\.([0-9]+)#i', $this->agent, $match)) {
			$info['major'] = (int) $match[1];
			$info['minor'] = $match[2];
			$info['version'] = "{$info['major']}.{$info['minor']}";
			
			if($info['major'] >= 7) {
				$info['f_css'] = 1;
				$info['f_js'] = 1.0;
			}
			if($info['major'] >= 8) {
				$info['f_css'] = 2;
				$info['f_js'] = 1.0;
				$info['f_utf8'] = true;
			}
			if($info['major'] >= 9) {
				$info['f_image'] = array('gif','jpeg','png','png-a','bmp');
			}
		}
		
		return $info;
	}

	/**
	 * parses the user agent string for os information
	 *
	 * @return array an array that contains the os information
	 */
	public function getOSInfo() {
		$info = array();
		$info['id'] = 'unknown';
		$info['name'] = 'unknown';
		$info['subid'] = 'unknown';
		$info['subname'] = '?';
		$info['major'] = '?';
		$info['minor'] = '?';
		$info['arch'] = '?';
		
		if(preg_match('#[^X](Win\s*(XP|NT|ME|CE|2000|98|95)|Windows)#i', $this->agent, $match)) {
			$info['id'] = 'win';
			$info['name'] = 'Windows';
			if(strtolower($match[1]) == 'windows') {
				if(preg_match('#Windows NT ([0-9]+)\.([0-9]+)#i', $this->agent, $match)) {
					$info['major'] = $match[1];
					$info['minor'] = $match[2];
					switch($match[1]) {
						case '4':
							$info['arch'] = 'x86';
							$info['subid'] = 'nt4';
							$info['subname'] = 'NT 4';
						break;
						case '5':
							$info['arch'] = 'x86';
							switch($match[2]) {
								case '0':
									$info['subid'] = '2k';
									$info['subname'] = '2000';
								break;
								case '1':
									$info['subid'] = 'xp';
									$info['subname'] = 'XP';
								break;
								case '2':
									$info['subid'] = '2k3';
									$info['subname'] = '2003/XP64';
								break;
							}
						break;
						case '6':
							$info['arch'] = 'x86';
							switch($match[2]) {
								case '0':
									$info['subid'] = 'vista';
									$info['subname'] = 'Vista';
								break;
								case '1':
									$info['subid'] = 'w7';
									$info['subname'] = '7';
								break;
							}
						break;
						default:
							$info['subid'] = 'nt-v' . $match[1] . '.' . $match[2];
							$info['subname'] = 'unknown version ' . $match[1] . '.' . $match[2];
						break;
					}
				}
				elseif(preg_match('#Windows\s*Phone\s*OS\s*([0-9]+)\.([0-9]+)#i', $this->agent, $match)) {
					$info['arch'] = 'arm';
					$info['subid'] = 'phone' . $match[1] . '.' . $match[2];
					$info['subname'] = 'Phone ' . $match[1] . '.' . $match[2];
				}
				elseif(preg_match('#Windows\s*Mobile\s*([0-9]+)\.([0-9]+)#i', $this->agent, $match)) {
					$info['arch'] = 'arm';
					$info['subid'] = 'mobile' . $match[1] . '.' . $match[2];
					$info['subname'] = 'Mobile ' . $match[1] . '.' . $match[2];
				}
				elseif(preg_match('#Windows\s*CE#i', $this->agent, $match)) {
					$info['arch'] = 'arm';
					$info['subid'] = 'ce';
					$info['subname'] = 'CE';
				}
			}
			elseif(!empty($match[2])) {
				$info['arch'] = 'x86';
				switch(strtolower($match[2])) {
					case 'xp':
						$info['subid'] = 'xp';
						$info['subname'] = 'XP';
					break;
					case 'me':
						$info['subid'] = 'me';
						$info['subname'] = 'Me';
					break;
					case '2000':
						$info['subid'] = '2k';
						$info['subname'] = '2000';
					break;
					case 'nt':
						$info['subid'] = 'ntx';
						$info['subname'] = 'ntx';
					break;
					case '95':
					case '98':
						$info['subid'] = '9x';
						$info['subname'] = '9x';
					break;
				}
			}
			if(preg_match('#WOW64#i', $this->agent)) {
				$info['arch'] = 'x86_64';
			}
			elseif(preg_match('#IA64#i', $this->agent)) {
				$info['arch'] = 'ia64';
			}
		}
		elseif(preg_match('#(Linux|Debian|Gentoo|SuSe)#i', $this->agent)) {
			$info['id'] = 'linux';
			$info['name'] = 'Linux';
			if(preg_match('#(Mandriva|Fedora|CertOS|Ubuntu|Kubuntu|Debian|Gentoo|SuSe|Red Hat)#i', $this->agent, $match)) {
				switch(strtolower($match[1])) {
					case 'mandriva':
						$info['subid'] = 'mandriva';
						$info['subname'] = 'Mandriva';
					break;
					case 'fedora':
						$info['subid'] = 'fedora';
						$info['subname'] = 'Fedora';
					break;
					case 'ubuntu':
						$info['subid'] = 'ubuntu';
						$info['subname'] = 'Ubuntu';
					break;
					case 'kubuntu':
						$info['subid'] = 'kubuntu';
						$info['subname'] = 'Kubuntu';
					break;
					case 'suse':
						$info['subid'] = 'suse';
						$info['subname'] = 'SUSE';
					break;
					case 'debian':
						$info['subid'] = 'debian';
						$info['subname'] = 'Debian';
					break;
					case 'certos':
						$info['subid'] = 'certos';
						$info['subname'] = 'CertOS';
					break;
					case 'gentoo':
						$info['subid'] = 'gentoo';
						$info['subname'] = 'Gentoo';
					break;
					case 'red hat':
						$info['subid'] = 'redhat';
						$info['subname'] = 'Red Hat';
					break;
				}
			}
			if(preg_match('#x86_64#i', $this->agent)) {
				$info['arch'] = 'x86_64';
			}
			elseif(preg_match('#x86#i', $this->agent)) {
				$info['arch'] = 'x86';
			}
		}
		elseif(preg_match('#(Free|Open|Net|Firefly)BSD#i', $this->agent, $match)) {
			$info['id'] = 'bsd';
			$info['name'] = 'BSD';
			$info['subname'] = ucfirst(strtolower($match[1]));
		}
		elseif(preg_match('#Mac#i', $this->agent)) {
			$info['id'] = 'mac';
			$info['name'] = 'Linux';
		}
		elseif(preg_match('#iPhone#i', $this->agent)) {
			$info['id'] = 'iphone';
			$info['name'] = 'iPhone';
		}
		elseif(preg_match('#J2ME#i', $this->agent)) {
			$info['id'] = 'javame';
			$info['name'] = 'Java2Mobile';
		}
		elseif(preg_match('#Solaris#i', $this->agent)) {
			$info['id'] = 'sun';
			$info['name'] = 'Solaris';
		}
		elseif(preg_match('#SymbianOS/([0-9]+)\.([0-9]+)#i', $this->agent, $match)) {
			$info['id'] = 'symbian';
			$info['name'] = 'SymbianOS';
			$info['major'] = $match[1];
			$info['minor'] = $match[2];
		}
		
		return $info;
	}

	/**
	 *
	 * @return boolean true if the agent runs on a small screen device
	 */
	public function isSmallScreen() {
		return $this->smallscreen;
	}

	public function isMobile() {
		return $this->mobile;
	}

	/**
	 *
	 * @return boolean true if the agent seems to run on a text terminal
	 */
	public function isConsole() {
		return $this->console;
	}

	/**
	 *
	 * @return boolean true if the currently active user agent seems to be a bot
	 */
	public function isBot() {
		return $this->bot;
	}

	/**
	 * checks if the currently active user agent is a bot, and if so, returns the bot
	 * owner
	 *
	 * @return mixed the ident of the bot service or false if the user agent is not a bot
	 */
	public function getBotService() {
		if(!$this->isBot())
			return false;
		foreach(self::$bots as $bot=>$regex) {
			if(preg_match('#' . $regex . '#i', $this->agent))
				return $bot;
		}
		return false;
	}
	private static $instance = false;

	/**
	 *
	 * @return UserAgent the browser instance of the active user agent
	 */
	public static function getInstance() {
		if(!self::$instance)
			self::$instance = new UserAgent();
		return self::$instance;
	}

	public function __toString() {
		return $this->agent;
	}
}