<?php
/**
* Classe FredT_Request_Info_UserAgent
*
* Roles:
* HTTP_USER_AGENT identification class
* - System detection (OS, device type TV, Console, Mobile ...)
* - "Agent" detection (Browser, Bot, Undesirable)
* - Many convenience function (isIE(), isBot(), isMobilSystem() ...)
* - Simple usage : new FredT_Request_Info_UserAgent() for detection of the current HTTP_USER_AGENT
* - or more advanced new FredT_Request_Info_UserAgent(userAgentParam) to stat usage for exemple
* - External file for perennity, facility update of the search data
* - Performance:
* . File cache for detection data
* . Dection mode (Fast, light, full)
*
* @author FredT
* @category FredT Library
* @package FredT Request Info
* @version 10/01/2009
*
* @todo (later), @see loadReference() function :
* - create xml (or/and ini) file : validation capicity for data and syntax
* - parsing code for ini/xml format
* - sort detection data
*
* @example see Tests PHPUnit
*
*
* Source:
* Many data from @see http://www.phpclasses.org/browse/package/4117.html (Class client_info 2007/09)
*
*/
/** FredT_Exception */
require_once(Exception.php');
class FredT_Request_Info_UserAgent
{
const BROWSER_IE = 'Internet Explorer';
const BROWSER_FIREFOX = 'Firefox';
const BROWSER_OPERA = 'Opera';
const BOT_GOOGLE = 'Googlebot';
const SYSTEM_UNKNOW = 'unknow';
const SYSTEM_MOBIL = 'mobil';
const SYSTEM_CONSOLE = 'console';
const SYSTEM_TV = 'tv';
const TYPE_BROWSER='browser';
const TYPE_BOT='bot';
const TYPE_UNDESIRABLE='undesirable';
const TYPE_UNKNOW = 'unknow';
const MODE_FULL_DETECT=3;
const MODE_LIGHT_DETECT=2;
const MODE_FAST_DETECT=1;
protected static $_refFile = '/Data/refUA.php';
/**
* Cache file
* @var string
*/
protected static $_fileCache;
/**
* @var array
*/
protected static $_reference=array();
protected static $_detectMode=self::MODE_FAST_DETECT;
/**
* User Agent
* @var string
*/
protected $_ua;
/**
* .NET Common Language Runtime
* @var bool
*/
protected $_NetClr;
/**
* @var string
*/
protected $_name;
/**
* @var string
*/
protected $_version;
/**
* @var string
*/
protected $_system;
/**
* @var string
*/
protected $_systemVersion;
/**
* @var string
*/
protected $_systemType;
/**
* @var string
*/
protected $_systemSubType;
/**
* @var string
*/
protected $_systemDescription;
/**
* @var string
*/
protected $_agentType;
/**
* @var string
*/
protected $_agentSubType;
/**
* @var string
*/
protected $_description;
/**
* constructor, can be use for define a userAgent string other than the actual,
* @see setDetectMode() function for performance
*
* @param string $userAgent defaut null for use the current userAgent
* @param unknown_type $mode
*/
public function __construct($userAgent=null, $mode=null)
{
self::setDetectMode($mode);
$this->setUserAgent($userAgent);
}
/**
* Set detection mode for performance
*
* @param MODE_*_DETECT $mode self::const default is MODE_FAST_DETECT
* @return void
*/
public static function setDetectMode($mode=null)
{
if (! is_null(self::$_detectMode) && is_null($mode)) {
return;
}
self::unloadReference();
if (is_null($mode) || ($mode!=self::MODE_FULL_DETECT && $mode!=self::MODE_LIGHT_DETECT) ) {
$mode=self::MODE_FAST_DETECT;
}
self::$_detectMode=$mode;
}
/**
* return detection mode
* @return const MODE_*_DETECT
*/
public static function getDetectMode()
{
return self::$_detectMode;
}
/**
* Definele user agent, no param or null for current user agent
*
* @param string $userAgent
* @return FredT_Request_Info_UserAgent
*/
public function setUserAgent($userAgent=null)
{
if (empty(self::$_reference)) {
self::loadReference();
}
if (null===$userAgent && isset($_SERVER['HTTP_USER_AGENT'])) {
$userAgent = $_SERVER['HTTP_USER_AGENT'];
}
elseif (null===$userAgent) {
$userAgent = getenv('HTTP_USER_AGENT');
}
elseif (! is_string($userAgent)) {
throw new FredT_Request_Exception('Invalid format for parameter userAgent!');
}
$this->_ua = $userAgent;
$this->_NetClr=null;
$this->_detectAgent(self::$_reference['agent']);
$this->_detectSystem(self::$_reference['system']);
return $this;
}
/**
* return the current used UserAgent
* @return String
*/
public function getUserAgent()
{
return $this->_ua;
}
/**
* return system name (win, linux ... for OS, Sony, nokia ... for mobil ...)
*
* @return string
*/
public function getSystem()
{
return $this->_system;
}
/**
* os, mobil, tv, consoles ... version
* @return string|null
*/
public function getSystemVersion()
{
return $this->_systemVersion;
}
/**
* return system type (const SYSTEM_*)
* @return string
*/
public function getSystemType()
{
return $this->_systemType;
}
/**
* Return system sub-type
* @return string|nul
*/
public function getSystemSubType()
{
return $this->_systemSubType;
}
/**
* Return system description
* @return string|null
*/
public function getSystemDescription()
{
return $this->_systemDescription;
}
/**
* Return agent type (const TYPE_* or other string)
* @return string
*/
public function getType()
{
return $this->_agentType;
}
/**
* Retourne s'il est défini le sous type du user agent détecté
* @return string|null
*/
public function getSubType()
{
return $this->_agentSubType;
}
/**
* Retourne le nom du user agent détecté, ou null si le type n'est pas détecté
* Peut renvoyer une des constantes FredT_Request_Info_UserAgent::BROWSER_*, ou BOT_* ...
* @return string|null
*/
public function getName()
{
return $this->_name;
}
/**
* Retourne la version du user agent détecté, ou null
* @return string|null
*/
public function getVersion()
{
return $this->_version;
}
/**
* Retourne l'éventuelle description si elle existe pour le user agent détecté
* @return string|null
*/
public function getDescription()
{
return $this->_description;
}
/**
* Get all infos in array
*
* @return array
*/
public function getInfos()
{
return array(
'user agent'=>$this->getUserAgent(),
'agent type'=>$this->getType(),
'agent sub-type'=>$this->getSubType(),
'agent name'=>$this->getName(),
'agent version'=>$this->getVersion(),
'agent description'=>$this->getDescription(),
'system name'=>$this->getSystem(),
'system version'=>$this->getSystemVersion(),
'system type'=>$this->getSystemType(),
'system sub-type'=>$this->getSystemSubType(),
'system description'=>$this->getSystemDescription(),
'NetClr'=>$this->isNET_CLR()
);
}
/**
* Retourne true si le user agent est de type bot
* @return bool
*/
public function isBot()
{
return (bool) ($this->getType()==self::TYPE_BOT);
}
/**
* Retourne true si le user agent est de type browser
* @return bool
*/
public function isBrowser()
{
return (bool) ($this->getType()==self::TYPE_BROWSER);
}
/**
* Retourne true si l'agent est de type inconnu et n'a pas été détecté
* @return bool
*/
public function isUnknow()
{
return (bool) ($this->getType()==self::TYPE_UNKNOW);
}
/**
* Retourne true si le system est de type inconnu et n'a pas été détecté
* @return bool
*/
public function isUnknowSystem()
{
return (bool) ($this->getSystemType()==self::SYSTEM_UNKNOW);
}
public function isMobilSystem()
{
return (bool) ($this->getSystemType()==self::SYSTEM_MOBIL);
}
public function isConsoleSystem()
{
return (bool) ($this->getSystemType()==self::SYSTEM_CONSOLE);
}
public function isTvSystem()
{
return (bool) ($this->getSystemType()==self::SYSTEM_TV);
}
/**
* Retourne true si le user agent est de type indésirable
* @return bool
*/
public function isUndesirable()
{
return (bool) ($this->getType()==self::TYPE_UNDESIRABLE);
}
/**
* Retourne true si le browser est IE
* @return bool
*/
public function isIE()
{
return ($this->getName()===self::BROWSER_IE);
}
/**
* Retourne true si le browser est Firefox
* @return bool
*/
public function isFirefox()
{
return ($this->getName()===self::BROWSER_FIREFOX);
}
/**
* Retourne true si le browser est Opera
* @return bool
*/
public function isOpera()
{
return ($this->getName()===self::BROWSER_OPERA);
}
/**
* Retourne true si est un bot google
* @return bool
*/
public function isGoogleBot()
{
return ($this->getName()===self::BOT_GOOGLE);
}
/**
* Return True if User Agent is .NET Common Language Runtime
* @return bool
*/
public function isNET_CLR() {
if (null === $this->_NetClr) {
$this->_NetClr = (bool) eregi('NET CLR',$this->_ua);
}
return $this->_NetClr;
}
/**
* System detection
* @param array $ref
*/
protected function _detectSystem($ref)
{
$this->_system=null;
$this->_systemVersion=null;
$this->_systemType=null;
$this->_systemSubType=null;
$this->_systemDescription=null;
$tmp_array = array();
foreach ($ref as $name => $elements) {
$exprReg=$elements['search'];
foreach ($exprReg as $expr) {
if(preg_match($expr, $this->getUserAgent(), $tmp_array)) {
$this->_system = $name;
if (isset($tmp_array) && isset($tmp_array[1])) {
if (isset($elements['version_subSearch'])) {
// sous-recherche de version version exemple Win
foreach ($elements['version_subSearch'] as $version => $expr) {
if(preg_match($expr, $tmp_array[1])) {
$this->_systemVersion = $version;
}
}
}
if($this->_systemVersion===null) {
$this->_systemVersion = (string) $tmp_array[1];
}
}
elseif (isset($elements['version_addSearch'])) {
// Pour version recherche additionnellle voir linux et bsd par exemple
foreach ($elements['version_addSearch'] as $version => $expr) {
if(preg_match($expr, $this->getUserAgent())) {
$this->_systemVersion = $version;
}
}
}
$this->_systemType=$elements['type'];
if (isset($elements['subType'])) {
$this->_systemSubType = $elements['subType'];
}
if (isset($elements['description'])) {
$this->_systemDescription = $elements['description'];
}
return ;
}
}
}
$this->_systemType = self::SYSTEM_UNKNOW;
}
/**
* Agent detection
* @param array $ref
*/
protected function _detectAgent($ref)
{
$this->_name=null;
$this->_version=null;
$this->_agentType=null;
$this->_agentSubType=null;
$this->_description=null;
$tmp_array = array();
foreach ($ref as $name => $elements) {
$exprReg=$elements['search'];
foreach ($exprReg as $expr) {
if(preg_match($expr, $this->getUserAgent(), $tmp_array)) {
$this->_name = $name;
if (isset($tmp_array) && isset($tmp_array[1])) {
$this->_version = (string) $tmp_array[1];
}
$this->_agentType=$elements['type'];
if (isset($elements['subType'])) {
$this->_agentSubType = $elements['subType'];
}
if (isset($elements['description'])) {
$this->_description = $elements['description'];
}
return ;
}
}
}
$this->_agentType = self::TYPE_UNKNOW;
}
/**
* Load data of the external file for detection
* - Use cache if his path is set
* - parse data detection for performance
* - Other file possibility (not tested)
*
* @param string $file|null
* @return void
*/
public static function loadReference($file=null)
{
if ( !is_null(self::getFileCache()) && is_null($file) ) {
$file= self::getFileCache();
}
elseif (is_null($file)) {
$file= dirname(__FILE__) . self::$_refFile;
}
elseif ( ! is_file($file) ) {
throw new FredT_Request_Exception('Reference file "'.$file.'" is not found!');
}
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION ));
switch ($ext) {
case 'php':
self::$_reference = include($file);
break;
case 'xml':
case 'ini':
// xxx créer ref par default au format xml ou ini pour modif plus facile
// Parsage du fichier ici
break;
default:
throw new FredT_Request_Exception('Invalid file format of "'.$file.'" for reference user agent!');
break;
}
while (list($detection, $detectionData) = each(self::$_reference)) {
while (list($name, $elements) = each($detectionData)) {
$mode=isset($elements['mode']) ? $elements['mode'] : self::MODE_FULL_DETECT;
if ( $mode>self::getDetectMode() ) {
unset(self::$_reference[$detection][$name]);
}
}
}
if ( !is_null(self::getFileCache()) ) return;
$ref = self::$_reference;
while (list($detection, $detectionData) = each($ref)) {
while (list($name, $elements) = each($detectionData)) {
$elements['search']= (array) $elements['search'];
while (list($i, $expr) = each($elements['search'])) {
if(!preg_match('/^\/.*\/si$/si', $expr)){
// si chaine simple, transforme en expr reguliere
$elements['search'][$i]='/' . $expr . '/si';
}
}
$detectionData[$name]=$elements;
}
$ref[$detection]=$detectionData;
/* xxx sort here
1 trie $exprReg ??
2 trie par type order undesirable->bot->browser
3 move 'Internet Explorer', 'Netscape Navigator', et 'Mozilla compatible' at the end?
*/
}
self::$_reference = $ref;
}
/**
* Convenience public function for test cache
* @return void
*/
public static function unloadReference() {
self::$_reference = array();
self::setFileCache(null);
}
/**
* Return reference detection array or a php string if $export = true
*
* @param bool $export
* @return array|string
*/
public static function getReference($export=false)
{
if (empty(self::$_reference)) {
self::loadReference();
}
$ret = self::$_reference;
if ($export) {
$ret=var_export($ret,true);
$ret=str_replace('\\\\','\\',$ret);
}
return $ret;
}
/**
* Set path to class file cache
* Specify a path to a file that will add the parsing refeference into php array.
* This is an opt-in feature for performance purposes.
*
* @param string $file
* @throws FredT_Request_Exception if file is not writeable or path does not exist
*/
public static function setFileCache($file)
{
if (null === $file) {
self::$_fileCache = null;
return ;
}
if (!file_exists($file) && !file_exists(dirname($file))) {
throw new FredT_Request_Exception('Specified file and his directory does not exist (' . $file . ')!');
}
if (file_exists($file) && !is_writable($file)) {
throw new FredT_Request_Exception('Specified file is not writeable (' . $file . ')');
}
if (!file_exists($file) && file_exists(dirname($file)) && !is_writable(dirname($file))) {
throw new FredT_Request_Exception('Specified directory is not writeable (' . $file . ')');
}
// create file
if (!file_exists($file)) {
self::unloadReference();
$oldMode= self::getDetectMode();
self::setDetectMode(self::MODE_FULL_DETECT);
$content = '<?php' . "\n" .
'return ' . self::getReference(true) . ';';
file_put_contents($file, $content);
self::setDetectMode($oldMode);
}
self::$_fileCache = $file;
}
/**
* Retrieve class file cache path
* @return string|null
*/
public static function getFileCache()
{
return self::$_fileCache;
}
}