You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PlexShare/fuel/core/classes/agent.php

658 lines
18 KiB

<?php
/**
* Part of the Fuel framework.
*
* @package Fuel
* @version 1.8
* @author Fuel Development Team
* @license MIT License
* @copyright 2010 - 2016 Fuel Development Team
* @link http://fuelphp.com
*/
namespace Fuel\Core;
/**
* Identifies the platform, browser, robot, or mobile device from the user agent string
*
* This class uses PHP's get_browser() to get details from the browsers user agent
* string. If not available, it can use a coded alternative using the php_browscap.ini
* file from http://browsers.garykeith.com.
*
* @package Fuel
* @subpackage Core
* @category Core
* @author Harro Verton
*/
class Agent
{
/**
* @var array information about the current browser
*/
protected static $properties = array(
'browser' => 'unknown',
'version' => 0,
'majorver' => 0,
'minorver' => 0,
'platform' => 'unknown',
'alpha' => false,
'beta' => false,
'win16' => false,
'win32' => false,
'win64' => false,
'frames' => false,
'iframes' => false,
'tables' => false,
'cookies' => false,
'backgroundsounds' => false,
'javascript' => false,
'vbscript' => false,
'javaapplets' => false,
'activexcontrols' => false,
'isbanned' => false,
'ismobiledevice' => false,
'issyndicationreader' => false,
'crawler' => false,
'cssversion' => 0,
'aolversion' => 0,
);
/**
* @var array property to cache key mapping
*/
protected static $keys = array(
'browser' => 'A',
'version' => 'B',
'majorver' => 'C',
'minorver' => 'D',
'platform' => 'E',
'alpha' => 'F',
'beta' => 'G',
'win16' => 'H',
'win32' => 'I',
'win64' => 'J',
'frames' => 'K',
'iframes' => 'L',
'tables' => 'M',
'cookies' => 'N',
'backgroundsounds' => 'O',
'javascript' => 'P',
'vbscript' => 'Q',
'javaapplets' => 'R',
'activexcontrols' => 'S',
'isbanned' => 'T',
'ismobiledevice' => 'U',
'issyndicationreader' => 'V',
'crawler' => 'W',
'cssversion' => 'X',
'aolversion' => 'Y',
);
/**
* @var array global config defaults
*/
protected static $defaults = array(
'browscap' => array(
'enabled' => true,
'url' => 'http://browscap.org/stream?q=Lite_PHP_BrowsCapINI',
'method' => 'wrapper',
'proxy' => array(
'host' => null,
'port' => null,
'auth' => 'none',
'username' => null,
'password' => null,
),
'file' => '',
),
'cache' => array(
'driver' => '',
'expiry' => 604800,
'identifier' => 'fuel.agent',
),
);
/**
* @var array global config items
*/
protected static $config = array(
);
/**
* @var string detected user agent string
*/
protected static $user_agent = '';
// --------------------------------------------------------------------
// public static methods
// --------------------------------------------------------------------
/**
* map the user agent string to browser specifications
*
* @return void
*/
public static function _init()
{
// fetch and store the user agent
static::$user_agent = \Input::server('http_user_agent', '');
// fetch and process the configuration
\Config::load('agent', true);
static::$config = array_merge(static::$defaults, \Config::get('agent', array()));
// validate the browscap configuration
if ( ! is_array(static::$config['browscap']))
{
static::$config['browscap'] = static::$defaults['browscap'];
}
else
{
if ( ! array_key_exists('enabled', static::$config['browscap']) or ! is_bool(static::$config['browscap']['enabled']))
{
static::$config['browscap']['enabled'] = true;
}
if ( ! array_key_exists('url', static::$config['browscap']) or ! is_string(static::$config['browscap']['url']))
{
static::$config['browscap']['url'] = static::$defaults['browscap']['url'];
}
if ( ! array_key_exists('file', static::$config['browscap']) or ! is_string(static::$config['browscap']['file']))
{
static::$config['browscap']['file'] = static::$defaults['browscap']['file'];
}
if ( ! array_key_exists('method', static::$config['browscap']) or ! is_string(static::$config['browscap']['method']))
{
static::$config['browscap']['method'] = static::$defaults['browscap']['method'];
}
static::$config['browscap']['method'] = strtolower(static::$config['browscap']['method']);
}
// validate the cache configuration
if ( ! is_array(static::$config['cache']))
{
static::$config['cache'] = static::$defaults['cache'];
}
else
{
if ( ! array_key_exists('driver', static::$config['cache']) or ! is_string(static::$config['cache']['driver']))
{
static::$config['cache']['driver'] = static::$defaults['cache']['driver'];
}
if ( ! array_key_exists('expiry', static::$config['cache']) or ! is_numeric(static::$config['cache']['expiry']) or static::$config['cache']['expiry'] < 7200)
{
static::$config['cache']['expiry'] = static::$defaults['cache']['expiry'];
}
if ( ! array_key_exists('identifier', static::$config['cache']) or ! is_string(static::$config['cache']['identifier']))
{
static::$config['cache']['identifier'] = static::$defaults['cache']['identifier'];
}
}
// do we have a user agent?
if (static::$user_agent)
{
// try the build in get_browser() method
if (static::$config['browscap']['method'] == 'local' or ini_get('browscap') == '' or false === $browser = get_browser(static::$user_agent, true))
{
// if it fails, emulate get_browser()
$browser = static::get_from_browscap();
}
if ($browser)
{
// save it for future reference
static::$properties = array_change_key_case($browser);
}
}
}
// --------------------------------------------------------------------
/**
* get the normalized browser name
*
* @return string
*/
public static function browser()
{
return static::$properties['browser'];
}
// --------------------------------------------------------------------
/**
* Get the browser platform
*
* @return string
*/
public static function platform()
{
return static::$properties['platform'];
}
// --------------------------------------------------------------------
/**
* Get the Browser Version
*
* @return string
*/
public static function version()
{
return static::$properties['version'];
}
// --------------------------------------------------------------------
/**
* Get any browser property
*
* @param string $property
* @return string|null
*/
public static function property($property = null)
{
$property = strtolower($property);
return array_key_exists($property, static::$properties) ? static::$properties[$property] : null;
}
// --------------------------------------------------------------------
/**
* Get all browser properties
*
* @return array
*/
public static function properties()
{
return static::$properties;
}
// --------------------------------------------------------------------
/**
* check if the current browser is a robot or crawler
*
* @return bool
*/
public static function is_robot()
{
return static::$properties['crawler'];
}
// --------------------------------------------------------------------
/**
* check if the current browser is mobile device
*
* @return bool
*/
public static function is_mobiledevice()
{
return static::$properties['ismobiledevice'];
}
// --------------------------------------------------------------------
/**
* check if the current browser accepts a specific language
*
* @param string $language optional ISO language code, defaults to 'en'
* @return bool
*/
public static function accepts_language($language = 'en')
{
return (in_array(strtolower($language), static::languages(), true)) ? true : false;
}
// --------------------------------------------------------------------
/**
* check if the current browser accepts a specific character set
*
* @param string $charset optional character set, defaults to 'utf-8'
* @return bool
*/
public static function accepts_charset($charset = 'utf-8')
{
return (in_array(strtolower($charset), static::charsets(), true)) ? true : false;
}
// --------------------------------------------------------------------
/**
* get the list of browser accepted languages
*
* @return array
*/
public static function languages()
{
return explode(',', preg_replace('/(;q=[0-9\.]+)/i', '', strtolower(trim(\Input::server('http_accept_language')))));
}
// --------------------------------------------------------------------
/**
* get the list of browser accepted charactersets
*
* @return array
*/
public static function charsets()
{
return explode(',', preg_replace('/(;q=.+)/i', '', strtolower(trim(\Input::server('http_accept_charset')))));
}
// --------------------------------------------------------------------
// internal static methods
// --------------------------------------------------------------------
/**
* use the parsed php_browscap.ini file to find a user agent match
*
* @return mixed array if a match is found, of false if not cached yet
*/
protected static function get_from_browscap()
{
$cache = \Cache::forge(static::$config['cache']['identifier'].'.browscap', static::$config['cache']['driver']);
// load the cached browscap data
try
{
$browscap = $cache->get();
}
// browscap not cached
catch (\Exception $e)
{
$browscap = static::$config['browscap']['enabled'] ? static::parse_browscap() : array();
}
$search = array('\*', '\?');
$replace = array('.*', '.');
$result = false;
// find a match for the user agent string
foreach($browscap as $browser => $properties)
{
$pattern = '@^'.str_replace($search, $replace, preg_quote($browser, '@')).'$@i';
if (preg_match($pattern, static::$user_agent))
{
// store the browser name
$properties['browser'] = $browser;
// fetch possible parent info
if (array_key_exists('Parent', $properties))
{
if ($properties['Parent'] > 0)
{
$parent = array_slice($browscap, $properties['Parent'], 1);
unset($properties['Parent']);
$properties = array_merge(current($parent), $properties);
// store the browser name
$properties['browser'] = key($parent);
}
}
// normalize keys
$properties = \Arr::replace_key($properties, array_flip(static::$keys));
// merge it with the defaults to add missing values
$result = array_merge(static::$properties, $properties);
break;
}
}
return $result;
}
// --------------------------------------------------------------------
/**
* download and parse the browscap file
*
* @return array array with parsed download info, or empty if the download is disabled of failed
* @throws \Exception
* @throws \FuelException
*/
protected static function parse_browscap()
{
$cache = \Cache::forge(static::$config['cache']['identifier'].'.browscap_file', static::$config['cache']['driver']);
// get the browscap.ini file
switch (static::$config['browscap']['method'])
{
case 'local':
if ( ! is_file(static::$config['browscap']['file']) or filesize(static::$config['browscap']['file']) == 0)
{
throw new \Exception('Agent class: could not open the local browscap.ini file: '.static::$config['browscap']['file']);
}
$data = @file_get_contents(static::$config['browscap']['file']);
break;
// socket connections are not implemented yet!
case 'sockets':
$data = false;
break;
case 'curl':
// initialize the proxy request
$curl = curl_init();
curl_setopt($curl, CURLOPT_BINARYTRANSFER, 1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_MAXREDIRS, 5);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_USERAGENT, 'Fuel PHP framework - Agent class (http://fuelphp.com)');
curl_setopt($curl, CURLOPT_URL, static::$config['browscap']['url']);
// add a proxy configuration if needed
if ( ! empty(static::$config['browscap']['proxy']['host']) and ! empty(static::$config['browscap']['proxy']['port']))
{
curl_setopt($curl, CURLOPT_PROXY, static::$config['browscap']['proxy']['host']);
curl_setopt($curl, CURLOPT_PROXYPORT, static::$config['browscap']['proxy']['port']);
}
// authentication set?
switch (static::$config['browscap']['proxy']['auth'])
{
case 'basic':
curl_setopt($curl, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
break;
case 'ntlm':
curl_setopt($curl, CURLOPT_PROXYAUTH, CURLAUTH_NTLM);
break;
default:
// no action
}
// do we need to pass credentials?
switch (static::$config['browscap']['proxy']['auth'])
{
case 'basic':
case 'ntlm':
if (empty(static::$config['browscap']['proxy']['username']) or empty(static::$config['browscap']['proxy']['password']))
{
logger(\Fuel::L_ERROR, 'Failed to set a proxy for Agent, cURL auth configured but no username or password configured');
}
else
{
curl_setopt($curl, CURLOPT_PROXYUSERPWD, static::$config['browscap']['proxy']['username'].':'.static::$config['browscap']['proxy']['password']);
}
break;
default:
// no action
}
// execute the request
$data = curl_exec($curl);
// check the response
$result = curl_getinfo($curl);
if ($result['http_code'] !== 200)
{
logger(\Fuel::L_ERROR, 'Failed to download browscap.ini file. cURL response code was '.$result['http_code'], 'Agent::parse_browscap');
logger(\Fuel::L_ERROR, $data);
$data = false;
}
break;
case 'wrapper':
// set our custom user agent
ini_set('user_agent', 'Fuel PHP framework - Agent class (http://fuelphp.com)');
// create a stream context if needed
$context = null;
if ( ! empty(static::$config['browscap']['proxy']['host']) and ! empty(static::$config['browscap']['proxy']['port']))
{
$context = array (
'http' => array (
'proxy' => 'tcp://'.static::$config['browscap']['proxy']['host'].':'.static::$config['browscap']['proxy']['port'],
'request_fulluri' => true,
),
);
}
// add credentials if needed
if ( ! empty(static::$config['browscap']['proxy']['auth']) and static::$config['browscap']['proxy']['auth'] == 'basic')
{
if ( ! empty(static::$config['browscap']['proxy']['username']) and ! empty(static::$config['browscap']['proxy']['password']))
{
$context['http']['header'] = 'Proxy-Authorization: Basic '.base64_encode(static::$config['browscap']['proxy']['username'].':'.static::$config['browscap']['proxy']['password']);
}
else
{
logger(\Fuel::L_ERROR, 'Failed to set a proxy for Agent, "basic" auth configured but no username or password configured');
$context = null;
}
}
// attempt to download the file
try
{
if ($context)
{
$context = stream_context_create($context);
}
$data = file_get_contents(static::$config['browscap']['url'], false, $context);
}
catch (\ErrorException $e)
{
logger(\Fuel::L_ERROR, 'Failed to download browscap.ini file.', 'Agent::parse_browscap');
logger(\Fuel::L_ERROR, $e->getMessage());
$data = false;
}
break;
default:
break;
}
if ($data === false)
{
// if no data could be download, try retrieving a cached version
try
{
$data = $cache->get(false);
// if the cached version is used, only cache the parsed result for a day
static::$config['cache']['expiry'] = 86400;
}
catch (\Exception $e)
{
logger(\Fuel::L_ERROR, 'Failed to get the cache of browscap.ini file.', 'Agent::parse_browscap');
}
}
else
{
// store the downloaded data in the cache as a backup for future use
$cache->set($data, null);
}
// parse the downloaded data
$browsers = @parse_ini_string($data, true, INI_SCANNER_RAW) or $browsers = array();
// remove the version and timestamp entry
array_shift($browsers);
$result = array();
// reverse sort on key string length
uksort($browsers, function($a, $b) { return strlen($a) < strlen($b) ? 1 : -1; } );
$index = array();
$count = 0;
// reduce the array keys
foreach($browsers as $browser => $properties)
{
$index[$browser] = $count++;
// fix any type issues
foreach ($properties as $var => $value)
{
if (is_numeric($value))
{
$properties[$var] = $value + 0;
}
elseif ($value == 'true')
{
$properties[$var] = true;
}
elseif ($value == 'false')
{
$properties[$var] = false;
}
}
$result[$browser] = \Arr::replace_key($properties, static::$keys);
}
// reduce parent links to
foreach($result as $browser => &$properties)
{
if (array_key_exists('Parent', $properties))
{
if ($properties['Parent'] == 'DefaultProperties')
{
unset($properties['Parent']);
}
else
{
if (array_key_exists($properties['Parent'], $index))
{
$properties['Parent'] = $index[$properties['Parent']];
}
else
{
unset($properties['Parent']);
}
}
}
}
// save the result to the cache
if ( ! empty($result))
{
$cache = \Cache::forge(static::$config['cache']['identifier'].'.browscap', static::$config['cache']['driver']);
$cache->set($result, static::$config['cache']['expiry']);
}
return $result;
}
}