<?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; } }