├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.markdown ├── composer.json ├── phpunit.xml.dist ├── src └── phpbrowscap │ └── Browscap.php └── tests ├── bootstrap.php ├── compare ├── compareWithOriginal.php ├── results.txt └── user-agent-examples.txt └── phpbrowscap ├── BrowscapTest.php └── TestCase.php /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | /.settings 3 | /.buildpath 4 | /.project 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ------- 3 | 4 | ### version 2.0 - February 4, 2014 5 | - Updated URLs to point to http://browscap.org/ 6 | 7 | ### version 2.0b (beta) - August 25, 2013 8 | - Added a new method to retrieve the source file version `$browscap->getSourceVersion()`. 9 | - Added a safety feature to regenerate the cache file always when `Browscap::CACHE_FILE_VERSION` changes. 10 | - Updated source file download URLs to new temporary URLs. 11 | - Added new lines `\n` to the cache files for readability. 12 | - Default download URL is changed so it will get and parse the full file instead of 'standard'. 13 | [ua-speed-tests](https://github.com/quentin389/ua-speed-tests) shows that there is only a small performance difference between 14 | using those two versions. 15 | - Performance upgrades (see [ua-speed-tests](https://github.com/quentin389/ua-speed-tests) for performance tests): 16 | * **5 times faster** for real user agents, with opcache on 17 | * **11 times faster** for user agents that do not match anything, with opcache on 18 | * **3 times faster** for real user agents, without using opcache 19 | * **5 times faster** for user agents that do not match anything, without using opcache 20 | * Regular expression pattern matches are being grouped by version numbers. The matches are performed 21 | in two stages. 1 - standard regular expression match with numbers that differ across source file 22 | patterns replaced with single character wildcard match. 2 - a check is performed on found numeric 23 | values to see if any of the grouped values are an exact match. If not, then the searching process resumes. 24 | This is the main source of the speed optimization. It greatly reduces the source file size and 25 | greatly increases matching performance. 26 | * Data that is not required to perform matches or return results was removed from the cache files. 27 | That includes the source file match strings, which can be recreated from the regex ones, and a large set 28 | of browser name entries which were never used because they had parents. Decreasing the cache file size 29 | is very important for when you don't use any PHP opcache, because loading large data structures into 30 | PHP takes a very long time. 31 | * Arrays that are not used in `foreach` loops were serialized in the cache file. This also decreases the time 32 | it takes to load the cache file when not using opcache. It's generally a very bad idea to load large arrays 33 | with subarrays into PHP. Serializing does a great job when optimizing performance. 34 | * The above changes address performance issues brought up in https://github.com/GaretJax/phpbrowscap/issues/26 35 | - Bug fixes: 36 | * Fixed https://github.com/GaretJax/phpbrowscap/issues/35 37 | * Fixed https://github.com/GaretJax/phpbrowscap/issues/34 38 | * Fixed https://github.com/GaretJax/phpbrowscap/issues/33 39 | * Fixed https://github.com/GaretJax/phpbrowscap/issues/32 40 | * Bug https://github.com/GaretJax/phpbrowscap/issues/17 is resolved, although that was fixed even before. 41 | * Merged https://github.com/GaretJax/phpbrowscap/pull/25 - those are mainly comment changes but there are also two fixes 42 | for `$browscap->clearProxySettings()` method, which did not work properly when an optional `$wrapper` parameter was passed. 43 | - Added a new testing class that compares result of `Browscap` to `get_browser()` for as many browsers as possible 44 | and checks if there are any differences in parsing. It also compares the parsing speed (in a simplistic way, 45 | more advanced tests are available at https://github.com/quentin389/ua-speed-tests). 46 | 47 | ### version 1.0 48 | - Initial version 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2010 Jonathan Stoppani 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Browser Capabilities PHP Project 2 | ================================ 3 | 4 | _Hacking around with PHP to have a better solution than `get_browser()`_ 5 | 6 | [![Build Status](https://secure.travis-ci.org/GaretJax/phpbrowscap.png?branch=master)](http://travis-ci.org/GaretJax/phpbrowscap) 7 | 8 | 9 | Changes (new version - 2.0) 10 | ------- 11 | 12 | Please see [changelog](CHANGELOG.md) for a list of recent changes. (huge performance improvements!) 13 | 14 | 15 | Introduction 16 | ------------ 17 | 18 | The [browscap.ini](http://tempdownloads.browserscap.com/) file is a database which 19 | provides a lot of details about browsers and their capabilities, such as name, 20 | versions, Javascript support and so on. 21 | 22 | _Please note: [browscap.ini](http://tempdownloads.browserscap.com/) was maintained by [Gary Keith](https://github.com/GaryKeith) and is 23 | now maintained by [RAD Moose](https://github.com/radmoose). More information about the transfer of owners can be found [here](https://groups.google.com/forum/#!topic/browscap/pk_dkkqdXzg). 24 | Browscap.ini source files are currently available at a temporary location (http://tempdownloads.browserscap.com/). 25 | All the links in `Browscap` class are updated, but if you use custom links remember to change them!_ 26 | 27 | PHP's native [get_browser()](http://php.net/get_browser) function parses this 28 | file and provides you with a complete set of information about every browser's 29 | details, But it requires the path to the browscap.ini file to be specified in 30 | the php.ini [browscap](http://ch2.php.net/manual/en/ref.misc.php#ini.browscap) 31 | directive which is flagged as `PHP_INI_SYSTEM`. 32 | 33 | Since in most shared hosting environments you have not access to the php.ini 34 | file, the browscap directive cannot be modified and you are stuck with either 35 | and outdated database or without browscap support at all. 36 | 37 | Browscap is a standalone class for PHP >=5.3 that gets around the limitations of 38 | `get_browser()` and manages the whole thing. 39 | It offers methods to update, cache, adapt and get details about every supplied 40 | user agent on a standalone basis. 41 | It's also much faster than `get_browser()` while still returning the same results. 42 | 43 | Browscap is a [Composer](http://packagist.org/about-composer) package. 44 | 45 | 46 | Quick start 47 | ----------- 48 | 49 | A quick start guide is available on the GitHub wiki, at the following address: 50 | https://github.com/GaretJax/phpbrowscap/wiki/QuickStart 51 | 52 | 53 | Features 54 | -------- 55 | 56 | Here is a non-exhaustive feature list of the Browscap class: 57 | 58 | * Very fast 59 | * at least 3 times faster than get_browser() when not using opcache 60 | * **20 or more** times faster than get_browser() when using opcache ([see tests](https://github.com/quentin389/ua-speed-tests)) 61 | * Standalone and fully PHP configuration independent (no need for php.ini setting) 62 | * Fully get_browser() compatible (with some get_browser() bugs fixed) 63 | * User agent auto-detection 64 | * Returns object or array 65 | * Parsed .ini file cached directly into PHP arrays (leverages opcache) 66 | * Accepts any .ini file (even ASP and lite versions) 67 | * Auto updated browscap.ini file and cache from remote server with version checking 68 | * Fully configurable, including configurable remote update server and update schedules 69 | * `PHP >= 5.3` compatible 70 | * Released under the MIT License 71 | 72 | 73 | Issues and feature requests 74 | --------------------------- 75 | 76 | Please report your issues and ask for new features on the GitHub Issue Tracker 77 | at https://github.com/GaretJax/phpbrowscap/issues 78 | 79 | Please report incorrectly identified User Agents and browser detect in the browscap.ini 80 | file on Google Groups here: https://groups.google.com/forum/#!forum/browscap 81 | 82 | Please note that the Browscap class only parses and queries the browscap.ini 83 | database provided by RAD Moose (previously by Gary Keith). If a browser is wrongly identified or a results 84 | presents erroneous properties, please refer directly to the temporary browscap project 85 | homepage at: http://tempdownloads.browserscap.com/ or post your misidentified browser and User Agent at 86 | the Browscap Google Groups page: https://groups.google.com/forum/#!forum/browscap 87 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "garetjax/phpbrowscap", 3 | "type": "library", 4 | "description": "Standalone replacement for php's native get_browser() function", 5 | "keywords": ["get_browser", "browser", "capabilities", "user agent"], 6 | "homepage": "http://github.com/GaretJax/phpbrowscap", 7 | "license": "MIT License", 8 | "authors": [ 9 | { 10 | "name": "Jonathan Stoppani", 11 | "email": "jonathan.stoppani@gmail.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.3.0" 16 | }, 17 | "autoload": { 18 | "psr-0": { "phpbrowscap": "src/" } 19 | } 20 | } -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | tests/phpbrowscap/ 7 | 8 | 9 | 10 | 11 | 12 | src/phpbrowscap/ 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/phpbrowscap/Browscap.php: -------------------------------------------------------------------------------- 1 | 34 | * @author Vítor Brandão 35 | * @author Mikołaj Misiurewicz 36 | * @copyright Copyright (c) 2006-2012 Jonathan Stoppani 37 | * @version 1.0 38 | * @license http://www.opensource.org/licenses/MIT MIT License 39 | * @link https://github.com/GaretJax/phpbrowscap/ 40 | */ 41 | class Browscap 42 | { 43 | /** 44 | * Current version of the class. 45 | */ 46 | const VERSION = '2.0'; 47 | 48 | const CACHE_FILE_VERSION = '2.0b'; 49 | 50 | /** 51 | * Different ways to access remote and local files. 52 | * 53 | * UPDATE_FOPEN: Uses the fopen url wrapper (use file_get_contents). 54 | * UPDATE_FSOCKOPEN: Uses the socket functions (fsockopen). 55 | * UPDATE_CURL: Uses the cURL extension. 56 | * UPDATE_LOCAL: Updates from a local file (file_get_contents). 57 | */ 58 | const UPDATE_FOPEN = 'URL-wrapper'; 59 | const UPDATE_FSOCKOPEN = 'socket'; 60 | const UPDATE_CURL = 'cURL'; 61 | const UPDATE_LOCAL = 'local'; 62 | 63 | /** 64 | * Options for regex patterns. 65 | * 66 | * REGEX_DELIMITER: Delimiter of all the regex patterns in the whole class. 67 | * REGEX_MODIFIERS: Regex modifiers. 68 | */ 69 | const REGEX_DELIMITER = '@'; 70 | const REGEX_MODIFIERS = 'i'; 71 | 72 | const COMPRESSION_PATTERN_START = '@'; 73 | const COMPRESSION_PATTERN_DELIMITER = '|'; 74 | 75 | /** 76 | * The values to quote in the ini file 77 | */ 78 | const VALUES_TO_QUOTE = 'Browser|Parent'; 79 | 80 | const BROWSCAP_VERSION_KEY = 'GJK_Browscap_Version'; 81 | 82 | /** 83 | * The headers to be sent for checking the version and requesting the file. 84 | */ 85 | const REQUEST_HEADERS = "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: %s\r\nConnection: Close\r\n\r\n"; 86 | 87 | /** 88 | * Options for auto update capabilities 89 | * 90 | * $remoteVerUrl: The location to use to check out if a new version of the 91 | * browscap.ini file is available. 92 | * $remoteIniUrl: The location from which download the ini file. 93 | * The placeholder for the file should be represented by a %s. 94 | * $timeout: The timeout for the requests. 95 | * $updateInterval: The update interval in seconds. 96 | * $errorInterval: The next update interval in seconds in case of an error. 97 | * $doAutoUpdate: Flag to disable the automatic interval based update. 98 | * $updateMethod: The method to use to update the file, has to be a value of 99 | * an UPDATE_* constant, null or false. 100 | * 101 | * The default source file type is changed from normal to full. The performance difference 102 | * is MINIMAL, so there is no reason to use the standard file whatsoever. Either go for light, 103 | * which is blazing fast, or get the full one. (note: light version doesn't work, a fix is on its way) 104 | */ 105 | public $remoteIniUrl = 'http://browscap.org/stream?q=Full_PHP_BrowsCapINI'; 106 | public $remoteVerUrl = 'http://browscap.org/version'; 107 | public $timeout = 5; 108 | public $updateInterval = 432000; // 5 days 109 | public $errorInterval = 7200; // 2 hours 110 | public $doAutoUpdate = true; 111 | public $updateMethod = null; 112 | 113 | /** 114 | * The path of the local version of the browscap.ini file from which to 115 | * update (to be set only if used). 116 | * 117 | * @var string 118 | */ 119 | public $localFile = null; 120 | 121 | /** 122 | * The useragent to include in the requests made by the class during the 123 | * update process. 124 | * 125 | * @var string 126 | */ 127 | public $userAgent = 'Browser Capabilities Project - PHP Browscap/%v %m'; 128 | 129 | /** 130 | * Flag to enable only lowercase indexes in the result. 131 | * The cache has to be rebuilt in order to apply this option. 132 | * 133 | * @var bool 134 | */ 135 | public $lowercase = false; 136 | 137 | /** 138 | * Flag to enable/disable silent error management. 139 | * In case of an error during the update process the class returns an empty 140 | * array/object if the update process can't take place and the browscap.ini 141 | * file does not exist. 142 | * 143 | * @var bool 144 | */ 145 | public $silent = false; 146 | 147 | /** 148 | * Where to store the cached PHP arrays. 149 | * 150 | * @var string 151 | */ 152 | public $cacheFilename = 'cache.php'; 153 | 154 | /** 155 | * Where to store the downloaded ini file. 156 | * 157 | * @var string 158 | */ 159 | public $iniFilename = 'browscap.ini'; 160 | 161 | /** 162 | * Path to the cache directory 163 | * 164 | * @var string 165 | */ 166 | public $cacheDir = null; 167 | 168 | /** 169 | * Flag to be set to true after loading the cache 170 | * 171 | * @var bool 172 | */ 173 | protected $_cacheLoaded = false; 174 | 175 | /** 176 | * Where to store the value of the included PHP cache file 177 | * 178 | * @var array 179 | */ 180 | protected $_userAgents = array(); 181 | protected $_browsers = array(); 182 | protected $_patterns = array(); 183 | protected $_properties = array(); 184 | protected $_source_version; 185 | 186 | /** 187 | * An associative array of associative arrays in the format 188 | * `$arr['wrapper']['option'] = $value` passed to stream_context_create() 189 | * when building a stream resource. 190 | * 191 | * Proxy settings are stored in this variable. 192 | * 193 | * @see http://www.php.net/manual/en/function.stream-context-create.php 194 | * 195 | * @var array 196 | */ 197 | protected $_streamContextOptions = array(); 198 | 199 | /** 200 | * A valid context resource created with stream_context_create(). 201 | * 202 | * @see http://www.php.net/manual/en/function.stream-context-create.php 203 | * 204 | * @var resource 205 | */ 206 | protected $_streamContext = null; 207 | 208 | /** 209 | * Constructor class, checks for the existence of (and loads) the cache and 210 | * if needed updated the definitions 211 | * 212 | * @param string $cache_dir 213 | * @throws Exception 214 | */ 215 | public function __construct($cache_dir) 216 | { 217 | // has to be set to reach E_STRICT compatibility, does not affect system/app settings 218 | date_default_timezone_set(date_default_timezone_get()); 219 | 220 | if (!isset($cache_dir)) { 221 | throw new Exception( 222 | 'You have to provide a path to read/store the browscap cache file' 223 | ); 224 | } 225 | 226 | $old_cache_dir = $cache_dir; 227 | $cache_dir = realpath($cache_dir); 228 | 229 | if (false === $cache_dir) { 230 | throw new Exception( 231 | sprintf('The cache path %s is invalid. Are you sure that it exists and that you have permission to access it?', $old_cache_dir) 232 | ); 233 | } 234 | 235 | // Is the cache dir really the directory or is it directly the file? 236 | if (substr($cache_dir, -4) === '.php') { 237 | $this->cacheFilename = basename($cache_dir); 238 | $this->cacheDir = dirname($cache_dir); 239 | } else { 240 | $this->cacheDir = $cache_dir; 241 | } 242 | 243 | $this->cacheDir .= DIRECTORY_SEPARATOR; 244 | } 245 | 246 | public function getSourceVersion() 247 | { 248 | return $this->_source_version; 249 | } 250 | 251 | /** 252 | * XXX parse 253 | * 254 | * Gets the information about the browser by User Agent 255 | * 256 | * @param string $user_agent the user agent string 257 | * @param bool $return_array whether return an array or an object 258 | * @throws Exception 259 | * @return stdClass|array the object containing the browsers details. Array if 260 | * $return_array is set to true. 261 | */ 262 | public function getBrowser($user_agent = null, $return_array = false) 263 | { 264 | // Load the cache at the first request 265 | if (!$this->_cacheLoaded) { 266 | $cache_file = $this->cacheDir . $this->cacheFilename; 267 | $ini_file = $this->cacheDir . $this->iniFilename; 268 | 269 | // Set the interval only if needed 270 | if ($this->doAutoUpdate && file_exists($ini_file)) { 271 | $interval = time() - filemtime($ini_file); 272 | } else { 273 | $interval = 0; 274 | } 275 | 276 | $update_cache = true; 277 | 278 | if (file_exists($cache_file) && file_exists($ini_file) && ($interval <= $this->updateInterval)) 279 | { 280 | if ($this->_loadCache($cache_file)) 281 | { 282 | $update_cache = false; 283 | } 284 | } 285 | 286 | if ($update_cache) { 287 | try { 288 | $this->updateCache(); 289 | } catch (Exception $e) { 290 | if (file_exists($ini_file)) { 291 | // Adjust the filemtime to the $errorInterval 292 | touch($ini_file, time() - $this->updateInterval + $this->errorInterval); 293 | } elseif ($this->silent) { 294 | // Return an array if silent mode is active and the ini db doesn't exsist 295 | return array(); 296 | } 297 | 298 | if (!$this->silent) { 299 | throw $e; 300 | } 301 | } 302 | 303 | if (!$this->_loadCache($cache_file)) 304 | { 305 | throw new Exception("Cannot load this cache version - the cache format is not compatible."); 306 | } 307 | } 308 | 309 | } 310 | 311 | // Automatically detect the useragent 312 | if (!isset($user_agent)) { 313 | if (isset($_SERVER['HTTP_USER_AGENT'])) { 314 | $user_agent = $_SERVER['HTTP_USER_AGENT']; 315 | } else { 316 | $user_agent = ''; 317 | } 318 | } 319 | 320 | $browser = array(); 321 | foreach ($this->_patterns as $pattern => $pattern_data) { 322 | if (preg_match($pattern . 'i', $user_agent, $matches)) { 323 | if (1 == count($matches)) { 324 | // standard match 325 | $key = $pattern_data; 326 | 327 | $simple_match = true; 328 | } else { 329 | $pattern_data = unserialize($pattern_data); 330 | 331 | // match with numeric replacements 332 | array_shift($matches); 333 | 334 | $match_string = self::COMPRESSION_PATTERN_START . implode(self::COMPRESSION_PATTERN_DELIMITER, $matches); 335 | 336 | if (!isset($pattern_data[$match_string])) { 337 | // partial match - numbers are not present, but everything else is ok 338 | continue; 339 | } 340 | 341 | $key = $pattern_data[$match_string]; 342 | 343 | $simple_match = false; 344 | } 345 | 346 | $browser = array( 347 | $user_agent, // Original useragent 348 | trim(strtolower($pattern), self::REGEX_DELIMITER), 349 | $this->_pregUnQuote($pattern, $simple_match ? false : $matches) 350 | ); 351 | 352 | $browser = $value = $browser + unserialize($this->_browsers[$key]); 353 | 354 | while (array_key_exists(3, $value)) { 355 | $value = unserialize($this->_browsers[$value[3]]); 356 | $browser += $value; 357 | } 358 | 359 | if (!empty($browser[3])) { 360 | $browser[3] = $this->_userAgents[$browser[3]]; 361 | } 362 | 363 | break; 364 | } 365 | } 366 | 367 | // Add the keys for each property 368 | $array = array(); 369 | foreach ($browser as $key => $value) { 370 | if ($value === 'true') { 371 | $value = true; 372 | } elseif ($value === 'false') { 373 | $value = false; 374 | } 375 | $array[$this->_properties[$key]] = $value; 376 | } 377 | 378 | return $return_array ? $array : (object) $array; 379 | } 380 | 381 | /** 382 | * Load (auto-set) proxy settings from environment variables. 383 | */ 384 | public function autodetectProxySettings() 385 | { 386 | $wrappers = array('http', 'https', 'ftp'); 387 | 388 | foreach ($wrappers as $wrapper) { 389 | $url = getenv($wrapper.'_proxy'); 390 | if (!empty($url)) { 391 | $params = array_merge(array( 392 | 'port' => null, 393 | 'user' => null, 394 | 'pass' => null, 395 | ), parse_url($url)); 396 | $this->addProxySettings($params['host'], $params['port'], $wrapper, $params['user'], $params['pass']); 397 | } 398 | } 399 | } 400 | 401 | /** 402 | * Add proxy settings to the stream context array. 403 | * 404 | * @param string $server Proxy server/host 405 | * @param int $port Port 406 | * @param string $wrapper Wrapper: "http", "https", "ftp", others... 407 | * @param string $username Username (when requiring authentication) 408 | * @param string $password Password (when requiring authentication) 409 | * 410 | * @return Browscap 411 | */ 412 | public function addProxySettings($server, $port = 3128, $wrapper = 'http', $username = null, $password = null) 413 | { 414 | $settings = array($wrapper => array( 415 | 'proxy' => sprintf('tcp://%s:%d', $server, $port), 416 | 'request_fulluri' => true, 417 | )); 418 | 419 | // Proxy authentication (optional) 420 | if (isset($username) && isset($password)) { 421 | $settings[$wrapper]['header'] = 'Proxy-Authorization: Basic '.base64_encode($username.':'.$password); 422 | } 423 | 424 | // Add these new settings to the stream context options array 425 | $this->_streamContextOptions = array_merge( 426 | $this->_streamContextOptions, 427 | $settings 428 | ); 429 | 430 | /* Return $this so we can chain addProxySettings() calls like this: 431 | * $browscap-> 432 | * addProxySettings('http')-> 433 | * addProxySettings('https')-> 434 | * addProxySettings('ftp'); 435 | */ 436 | return $this; 437 | } 438 | 439 | /** 440 | * Clear proxy settings from the stream context options array. 441 | * 442 | * @param string $wrapper Remove settings from this wrapper only 443 | * 444 | * @return array Wrappers cleared 445 | */ 446 | public function clearProxySettings($wrapper = null) 447 | { 448 | $wrappers = isset($wrapper) ? array($wrapper) : array_keys($this->_streamContextOptions); 449 | 450 | $clearedWrappers = array(); 451 | $options = array('proxy', 'request_fulluri', 'header'); 452 | foreach ($wrappers as $wrapper) { 453 | 454 | // remove wrapper options related to proxy settings 455 | if (isset($this->_streamContextOptions[$wrapper]['proxy'])) { 456 | foreach ($options as $option){ 457 | unset($this->_streamContextOptions[$wrapper][$option]); 458 | } 459 | 460 | // remove wrapper entry if there are no other options left 461 | if (empty($this->_streamContextOptions[$wrapper])) { 462 | unset($this->_streamContextOptions[$wrapper]); 463 | } 464 | 465 | $clearedWrappers[] = $wrapper; 466 | } 467 | } 468 | 469 | return $clearedWrappers; 470 | } 471 | 472 | /** 473 | * Returns the array of stream context options. 474 | * 475 | * @return array 476 | */ 477 | public function getStreamContextOptions() 478 | { 479 | return $this->_streamContextOptions; 480 | } 481 | 482 | /** 483 | * XXX save 484 | * 485 | * Parses the ini file and updates the cache files 486 | * 487 | * @return bool whether the file was correctly written to the disk 488 | */ 489 | public function updateCache() 490 | { 491 | $ini_path = $this->cacheDir . $this->iniFilename; 492 | $cache_path = $this->cacheDir . $this->cacheFilename; 493 | 494 | // Choose the right url 495 | if ($this->_getUpdateMethod() == self::UPDATE_LOCAL) { 496 | $url = $this->localFile; 497 | } else { 498 | $url = $this->remoteIniUrl; 499 | } 500 | 501 | $this->_getRemoteIniFile($url, $ini_path); 502 | 503 | if (version_compare(PHP_VERSION, '5.3.0', '>=')) { 504 | $browsers = parse_ini_file($ini_path, true, INI_SCANNER_RAW); 505 | } else { 506 | $browsers = parse_ini_file($ini_path, true); 507 | } 508 | 509 | $this->_source_version = $browsers[self::BROWSCAP_VERSION_KEY]['Version']; 510 | unset($browsers[self::BROWSCAP_VERSION_KEY]); 511 | 512 | unset($browsers['DefaultProperties']['RenderingEngine_Description']); 513 | 514 | $this->_properties = array_keys($browsers['DefaultProperties']); 515 | 516 | array_unshift( 517 | $this->_properties, 518 | 'browser_name', 519 | 'browser_name_regex', 520 | 'browser_name_pattern', 521 | 'Parent' 522 | ); 523 | 524 | $tmp_user_agents = array_keys($browsers); 525 | 526 | 527 | usort($tmp_user_agents, array($this, 'compareBcStrings')); 528 | 529 | $user_agents_keys = array_flip($tmp_user_agents); 530 | $properties_keys = array_flip($this->_properties); 531 | 532 | $tmp_patterns = array(); 533 | 534 | foreach ($tmp_user_agents as $i => $user_agent) { 535 | 536 | if (empty($browsers[$user_agent]['Comment']) || strpos($user_agent, '*') !== false || strpos($user_agent, '?') !== false) 537 | { 538 | $pattern = $this->_pregQuote($user_agent); 539 | 540 | $matches_count = preg_match_all('@\d@', $pattern, $matches); 541 | 542 | if (!$matches_count) { 543 | $tmp_patterns[$pattern] = $i; 544 | } else { 545 | $compressed_pattern = preg_replace('@\d@', '(\d)', $pattern); 546 | 547 | if (!isset($tmp_patterns[$compressed_pattern])) { 548 | $tmp_patterns[$compressed_pattern] = array('first' => $pattern); 549 | } 550 | 551 | $tmp_patterns[$compressed_pattern][$i] = $matches[0]; 552 | } 553 | } 554 | 555 | if (!empty($browsers[$user_agent]['Parent'])) { 556 | $parent = $browsers[$user_agent]['Parent']; 557 | $parent_key = $user_agents_keys[$parent]; 558 | $browsers[$user_agent]['Parent'] = $parent_key; 559 | $this->_userAgents[$parent_key . '.0'] = $tmp_user_agents[$parent_key]; 560 | }; 561 | 562 | $browser = array(); 563 | foreach ($browsers[$user_agent] as $key => $value) { 564 | if (!isset($properties_keys[$key])) 565 | { 566 | continue; 567 | } 568 | 569 | $key = $properties_keys[$key]; 570 | $browser[$key] = $value; 571 | } 572 | 573 | 574 | $this->_browsers[] = $browser; 575 | } 576 | 577 | foreach ($tmp_patterns as $pattern => $pattern_data) { 578 | if (is_int($pattern_data)) { 579 | $this->_patterns[$pattern] = $pattern_data; 580 | } elseif (2 == count($pattern_data)) { 581 | end($pattern_data); 582 | $this->_patterns[$pattern_data['first']] = key($pattern_data); 583 | } else { 584 | unset($pattern_data['first']); 585 | 586 | $pattern_data = $this->deduplicateCompressionPattern($pattern_data, $pattern); 587 | 588 | $this->_patterns[$pattern] = $pattern_data; 589 | } 590 | } 591 | 592 | // Save the keys lowercased if needed 593 | if ($this->lowercase) { 594 | $this->_properties = array_map('strtolower', $this->_properties); 595 | } 596 | 597 | // Get the whole PHP code 598 | $cache = $this->_buildCache(); 599 | 600 | // Save and return 601 | return (bool) file_put_contents($cache_path, $cache, LOCK_EX); 602 | } 603 | 604 | protected function compareBcStrings($a, $b) 605 | { 606 | $a_len = strlen($a); 607 | $b_len = strlen($b); 608 | 609 | if ($a_len > $b_len) return -1; 610 | if ($a_len < $b_len) return 1; 611 | 612 | $a_len = strlen(str_replace(array('*', '?'), '', $a)); 613 | $b_len = strlen(str_replace(array('*', '?'), '', $b)); 614 | 615 | if ($a_len > $b_len) return -1; 616 | if ($a_len < $b_len) return 1; 617 | 618 | return 0; 619 | } 620 | 621 | /** 622 | * That looks complicated... 623 | * 624 | * All numbers are taken out into $matches, so we check if any of those numbers are identical 625 | * in all the $matches and if they are we restore them to the $pattern, removing from the $matches. 626 | * This gives us patterns with "(\d)" only in places that differ for some matches. 627 | * 628 | * @param array $matches 629 | * @param string $pattern 630 | * 631 | * @return array of $matches 632 | */ 633 | protected function deduplicateCompressionPattern($matches, &$pattern) 634 | { 635 | $tmp_matches = $matches; 636 | 637 | $first_match = array_shift($tmp_matches); 638 | 639 | $differences = array(); 640 | 641 | foreach ($tmp_matches as $some_match) 642 | { 643 | $differences += array_diff_assoc($first_match, $some_match); 644 | } 645 | 646 | $identical = array_diff_key($first_match, $differences); 647 | 648 | $prepared_matches = array(); 649 | 650 | foreach ($matches as $i => $some_match) 651 | { 652 | $prepared_matches[self::COMPRESSION_PATTERN_START . implode(self::COMPRESSION_PATTERN_DELIMITER, array_diff_assoc($some_match, $identical))] = $i; 653 | } 654 | 655 | $pattern_parts = explode('(\d)', $pattern); 656 | 657 | foreach ($identical as $position => $value) 658 | { 659 | $pattern_parts[$position + 1] = $pattern_parts[$position] . $value . $pattern_parts[$position + 1]; 660 | unset($pattern_parts[$position]); 661 | } 662 | 663 | $pattern = implode('(\d)', $pattern_parts); 664 | 665 | return $prepared_matches; 666 | } 667 | 668 | /** 669 | * Converts browscap match patterns into preg match patterns. 670 | * 671 | * @param string $user_agent 672 | * 673 | * @return string 674 | */ 675 | protected function _pregQuote($user_agent) 676 | { 677 | $pattern = preg_quote($user_agent, self::REGEX_DELIMITER); 678 | 679 | // the \\x replacement is a fix for "Der gro\xdfe BilderSauger 2.00u" user agent match 680 | 681 | return self::REGEX_DELIMITER 682 | . '^' 683 | . str_replace(array('\*', '\?', '\\x'), array('.*', '.', '\\\\x'), $pattern) 684 | . '$' 685 | . self::REGEX_DELIMITER; 686 | } 687 | 688 | /** 689 | * Converts preg match patterns back to browscap match patterns. 690 | * 691 | * @param string $pattern 692 | * @param array $matches 693 | * 694 | * @return string 695 | */ 696 | protected function _pregUnQuote($pattern, $matches) 697 | { 698 | // list of escaped characters: http://www.php.net/manual/en/function.preg-quote.php 699 | // to properly unescape '?' which was changed to '.', I replace '\.' (real dot) with '\?', then change '.' to '?' and then '\?' to '.'. 700 | $search = array('\\' . self::REGEX_DELIMITER, '\\.', '\\\\', '\\+', '\\[', '\\^', '\\]', '\\$', '\\(', '\\)', '\\{', '\\}', '\\=', '\\!', '\\<', '\\>', '\\|', '\\:', '\\-', '.*', '.', '\\?'); 701 | $replace = array(self::REGEX_DELIMITER, '\\?', '\\', '+', '[', '^', ']', '$', '(', ')', '{', '}', '=', '!', '<', '>', '|', ':', '-', '*', '?', '.'); 702 | 703 | $result = substr(str_replace($search, $replace, $pattern), 2, -2); 704 | 705 | if ($matches) 706 | { 707 | foreach ($matches as $one_match) 708 | { 709 | $num_pos = strpos($result, '(\d)'); 710 | $result = substr_replace($result, $one_match, $num_pos, 4); 711 | } 712 | } 713 | 714 | return $result; 715 | } 716 | 717 | /** 718 | * Loads the cache into object's properties 719 | * 720 | * @param $cache_file 721 | * 722 | * @return boolean 723 | */ 724 | protected function _loadCache($cache_file) 725 | { 726 | require $cache_file; 727 | 728 | if (!isset($cache_version) || $cache_version != self::CACHE_FILE_VERSION) 729 | { 730 | return false; 731 | } 732 | 733 | $this->_source_version = $source_version; 734 | $this->_browsers = $browsers; 735 | $this->_userAgents = $userAgents; 736 | $this->_patterns = $patterns; 737 | $this->_properties = $properties; 738 | 739 | $this->_cacheLoaded = true; 740 | 741 | return true; 742 | } 743 | 744 | /** 745 | * Parses the array to cache and creates the PHP string to write to disk 746 | * 747 | * @return string the PHP string to save into the cache file 748 | */ 749 | protected function _buildCache() 750 | { 751 | $cacheTpl = "_array2string($this->_properties); 754 | $patternsArray = $this->_array2string($this->_patterns); 755 | $userAgentsArray = $this->_array2string($this->_userAgents); 756 | $browsersArray = $this->_array2string($this->_browsers); 757 | 758 | return sprintf( 759 | $cacheTpl, 760 | "'" . $this->_source_version . "'", 761 | "'" . self::CACHE_FILE_VERSION . "'", 762 | $propertiesArray, 763 | $browsersArray, 764 | $userAgentsArray, 765 | $patternsArray 766 | ); 767 | } 768 | 769 | /** 770 | * Lazy getter for the stream context resource. 771 | * 772 | * @param bool $recreate 773 | * 774 | * @return resource 775 | */ 776 | protected function _getStreamContext($recreate = false) 777 | { 778 | if (!isset($this->_streamContext) || true === $recreate) { 779 | $this->_streamContext = stream_context_create($this->_streamContextOptions); 780 | } 781 | 782 | return $this->_streamContext; 783 | } 784 | 785 | /** 786 | * Updates the local copy of the ini file (by version checking) and adapts 787 | * his syntax to the PHP ini parser 788 | * 789 | * @param string $url the url of the remote server 790 | * @param string $path the path of the ini file to update 791 | * @throws Exception 792 | * @return bool if the ini file was updated 793 | */ 794 | protected function _getRemoteIniFile($url, $path) 795 | { 796 | // Check version 797 | if (file_exists($path) && filesize($path)) { 798 | $local_tmstp = filemtime($path); 799 | 800 | if ($this->_getUpdateMethod() == self::UPDATE_LOCAL) { 801 | $remote_tmstp = $this->_getLocalMTime(); 802 | } else { 803 | $remote_tmstp = $this->_getRemoteMTime(); 804 | } 805 | 806 | if ($remote_tmstp < $local_tmstp) { 807 | // No update needed, return 808 | touch($path); 809 | 810 | return false; 811 | } 812 | } 813 | 814 | // Get updated .ini file 815 | $browscap = $this->_getRemoteData($url); 816 | 817 | 818 | $browscap = explode("\n", $browscap); 819 | 820 | $pattern = self::REGEX_DELIMITER 821 | . '(' 822 | . self::VALUES_TO_QUOTE 823 | . ')="?([^"]*)"?$' 824 | . self::REGEX_DELIMITER; 825 | 826 | 827 | // Ok, lets read the file 828 | $content = ''; 829 | foreach ($browscap as $subject) { 830 | $subject = trim($subject); 831 | $content .= preg_replace($pattern, '$1="$2"', $subject) . "\n"; 832 | } 833 | 834 | if ($url != $path) { 835 | if (!file_put_contents($path, $content)) { 836 | throw new Exception("Could not write .ini content to $path"); 837 | } 838 | } 839 | 840 | return true; 841 | } 842 | 843 | /** 844 | * Gets the remote ini file update timestamp 845 | * 846 | * @throws Exception 847 | * @return int the remote modification timestamp 848 | */ 849 | protected function _getRemoteMTime() 850 | { 851 | $remote_datetime = $this->_getRemoteData($this->remoteVerUrl); 852 | $remote_tmstp = strtotime($remote_datetime); 853 | 854 | if (!$remote_tmstp) { 855 | throw new Exception("Bad datetime format from {$this->remoteVerUrl}"); 856 | } 857 | 858 | return $remote_tmstp; 859 | } 860 | 861 | /** 862 | * Gets the local ini file update timestamp 863 | * 864 | * @throws Exception 865 | * @return int the local modification timestamp 866 | */ 867 | protected function _getLocalMTime() 868 | { 869 | if (!is_readable($this->localFile) || !is_file($this->localFile)) { 870 | throw new Exception("Local file is not readable"); 871 | } 872 | 873 | return filemtime($this->localFile); 874 | } 875 | 876 | /** 877 | * Converts the given array to the PHP string which represent it. 878 | * This method optimizes the PHP code and the output differs form the 879 | * var_export one as the internal PHP function does not strip whitespace or 880 | * convert strings to numbers. 881 | * 882 | * @param array $array the array to parse and convert 883 | * @return string the array parsed into a PHP string 884 | */ 885 | protected function _array2string($array) 886 | { 887 | $strings = array(); 888 | 889 | foreach ($array as $key => $value) { 890 | if (is_int($key)) { 891 | $key = ''; 892 | } elseif (ctype_digit((string) $key) || '.0' === substr($key, -2)) { 893 | $key = intval($key) . '=>' ; 894 | } else { 895 | $key = "'" . str_replace("'", "\'", $key) . "'=>" ; 896 | } 897 | 898 | if (is_array($value)) { 899 | $value = "'" . addcslashes(serialize($value), "'") . "'"; 900 | } elseif (ctype_digit((string) $value)) { 901 | $value = intval($value); 902 | } else { 903 | $value = "'" . str_replace("'", "\'", $value) . "'"; 904 | } 905 | 906 | $strings[] = $key . $value; 907 | } 908 | 909 | return "array(\n" . implode(",\n", $strings) . "\n)"; 910 | } 911 | 912 | /** 913 | * Checks for the various possibilities offered by the current configuration 914 | * of PHP to retrieve external HTTP data 915 | * 916 | * @return string the name of function to use to retrieve the file 917 | */ 918 | protected function _getUpdateMethod() 919 | { 920 | // Caches the result 921 | if ($this->updateMethod === null) { 922 | if ($this->localFile !== null) { 923 | $this->updateMethod = self::UPDATE_LOCAL; 924 | } elseif (ini_get('allow_url_fopen') && function_exists('file_get_contents')) { 925 | $this->updateMethod = self::UPDATE_FOPEN; 926 | } elseif (function_exists('fsockopen')) { 927 | $this->updateMethod = self::UPDATE_FSOCKOPEN; 928 | } elseif (extension_loaded('curl')) { 929 | $this->updateMethod = self::UPDATE_CURL; 930 | } else { 931 | $this->updateMethod = false; 932 | } 933 | } 934 | 935 | return $this->updateMethod; 936 | } 937 | 938 | /** 939 | * Retrieve the data identified by the URL 940 | * 941 | * @param string $url the url of the data 942 | * @throws Exception 943 | * @return string the retrieved data 944 | */ 945 | protected function _getRemoteData($url) 946 | { 947 | ini_set('user_agent', $this->_getUserAgent()); 948 | 949 | switch ($this->_getUpdateMethod()) { 950 | case self::UPDATE_LOCAL: 951 | $file = file_get_contents($url); 952 | 953 | if ($file !== false) { 954 | return $file; 955 | } else { 956 | throw new Exception('Cannot open the local file'); 957 | } 958 | case self::UPDATE_FOPEN: 959 | // include proxy settings in the file_get_contents() call 960 | $context = $this->_getStreamContext(); 961 | $file = file_get_contents($url, false, $context); 962 | 963 | if ($file !== false) { 964 | return $file; 965 | } // else try with the next possibility (break omitted) 966 | case self::UPDATE_FSOCKOPEN: 967 | $remote_url = parse_url($url); 968 | $remote_handler = fsockopen($remote_url['host'], 80, $c, $e, $this->timeout); 969 | 970 | if ($remote_handler) { 971 | stream_set_timeout($remote_handler, $this->timeout); 972 | 973 | if (isset($remote_url['query'])) { 974 | $remote_url['path'] .= '?' . $remote_url['query']; 975 | } 976 | 977 | $out = sprintf( 978 | self::REQUEST_HEADERS, 979 | $remote_url['path'], 980 | $remote_url['host'], 981 | $this->_getUserAgent() 982 | ); 983 | 984 | fwrite($remote_handler, $out); 985 | 986 | $response = fgets($remote_handler); 987 | if (strpos($response, '200 OK') !== false) { 988 | $file = ''; 989 | while (!feof($remote_handler)) { 990 | $file .= fgets($remote_handler); 991 | } 992 | 993 | $file = str_replace("\r\n", "\n", $file); 994 | $file = explode("\n\n", $file); 995 | array_shift($file); 996 | 997 | $file = implode("\n\n", $file); 998 | 999 | fclose($remote_handler); 1000 | 1001 | return $file; 1002 | } 1003 | } // else try with the next possibility 1004 | case self::UPDATE_CURL: 1005 | $ch = curl_init($url); 1006 | 1007 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 1008 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout); 1009 | curl_setopt($ch, CURLOPT_USERAGENT, $this->_getUserAgent()); 1010 | 1011 | $file = curl_exec($ch); 1012 | 1013 | curl_close($ch); 1014 | 1015 | if ($file !== false) { 1016 | return $file; 1017 | } // else try with the next possibility 1018 | case false: 1019 | throw new Exception('Your server can\'t connect to external resources. Please update the file manually.'); 1020 | } 1021 | 1022 | return ''; 1023 | } 1024 | 1025 | /** 1026 | * Format the useragent string to be used in the remote requests made by the 1027 | * class during the update process. 1028 | * 1029 | * @return string the formatted user agent 1030 | */ 1031 | protected function _getUserAgent() 1032 | { 1033 | $ua = str_replace('%v', self::VERSION, $this->userAgent); 1034 | $ua = str_replace('%m', $this->_getUpdateMethod(), $ua); 1035 | 1036 | return $ua; 1037 | } 1038 | } 1039 | 1040 | /** 1041 | * Browscap.ini parsing class exception 1042 | * 1043 | * @package Browscap 1044 | * @author Jonathan Stoppani 1045 | * @copyright Copyright (c) 2006-2012 Jonathan Stoppani 1046 | * @version 1.0 1047 | * @license http://www.opensource.org/licenses/MIT MIT License 1048 | * @link https://github.com/GaretJax/phpbrowscap/*/ 1049 | class Exception extends BaseException 1050 | {} 1051 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 30 | * @copyright Copyright (c) 2006-2012 Jonathan Stoppani 31 | * @version 1.0 32 | * @license http://www.opensource.org/licenses/MIT MIT License 33 | * @link https://github.com/GaretJax/phpbrowscap/ 34 | */ 35 | 36 | require_once __DIR__.'/phpbrowscap/TestCase.php'; 37 | 38 | spl_autoload_register(function($class) 39 | { 40 | $file = __DIR__.'/../src/'.strtr($class, '\\', '/').'.php'; 41 | if (file_exists($file)) { 42 | require $file; 43 | return true; 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /tests/compare/compareWithOriginal.php: -------------------------------------------------------------------------------- 1 | browscap = new Browscap(self::$base_dir . 'cache/'); 465 | 466 | $this->browscap_ini_path = ini_get('browscap'); 467 | 468 | $this->browscap->localFile = $this->browscap_ini_path; 469 | $this->browscap->updateMethod = Browscap::UPDATE_LOCAL; 470 | 471 | $this->getUserAgents(); 472 | 473 | $this->checkProperties(); 474 | 475 | $this->runTest(); 476 | } 477 | 478 | protected function runTest() 479 | { 480 | echo "\n"; 481 | 482 | $errors_count = 0; 483 | $warnings_count = 0; 484 | $lib_time = 0; 485 | $lib_max_time = 0; 486 | $bc_time = 0; 487 | $bc_max_time = 0; 488 | 489 | foreach ($this->user_agents as $i => $user_agent) 490 | { 491 | $t = microtime(true); 492 | $lib_result = get_browser($user_agent); 493 | $ct = microtime(true) - $t; 494 | $lib_time += $ct; 495 | $lib_max_time = max($lib_max_time, $ct); 496 | 497 | $t = microtime(true); 498 | $bc_result = $this->browscap->getBrowser($user_agent); 499 | $ct = microtime(true) - $t; 500 | $bc_time += $ct; 501 | $bc_max_time = max($bc_max_time, $ct); 502 | 503 | $errors = array(); 504 | 505 | if ($user_agent == Browscap::BROWSCAP_VERSION_KEY) 506 | { 507 | if ($this->browscap->getSourceVersion() != $lib_result->version) 508 | { 509 | $errors[] = "Source file version incorrect: {$lib_result->version} != {$this->browscap->getSourceVersion()}"; 510 | } 511 | } 512 | else foreach ($this->properties as $bc_prop => $lib_prop) 513 | { 514 | $lib_value = $lib_result->{$lib_prop}; 515 | 516 | $bc_value = $bc_result->{$bc_prop}; 517 | 518 | if ($lib_value != $bc_value) 519 | { 520 | $errors[] = "$bc_prop: $lib_value != $bc_value"; 521 | } 522 | } 523 | 524 | if ($errors && in_array($user_agent, $this->warnings)) 525 | { 526 | $warnings_count++; 527 | 528 | echo "get_browser() error fixed for '$user_agent'\n\n"; 529 | } 530 | elseif ($errors) 531 | { 532 | $errors_count++; 533 | 534 | $errors[] = "regex: '{$lib_result->browser_name_regex}' vs '{$bc_result->browser_name_regex}'"; 535 | 536 | echo "Errors for '$user_agent'\n" . implode("\n", $errors) . "\n\n"; 537 | } 538 | 539 | if ($i % 500 == 0 && $i != 0) 540 | { 541 | $this->printReport($i, $errors_count, $warnings_count, $lib_time, $lib_max_time, $bc_time, $bc_max_time); 542 | } 543 | } 544 | 545 | $this->printReport($i, $errors_count, $warnings_count, $lib_time, $lib_max_time, $bc_time, $bc_max_time); 546 | } 547 | 548 | protected function printReport($i, $errors_count, $warnings_count, $lib_time, $lib_max_time, $bc_time, $bc_max_time) 549 | { 550 | $lt = number_format($lib_time, 2) . ' sec'; 551 | $ltpp = number_format($lib_time / $i * 1000, 1) . ' ms / item'; 552 | $ltm = number_format($lib_max_time * 1000, 1) . ' ms'; 553 | 554 | $bt = number_format($bc_time, 2) . ' sec'; 555 | $btpp = number_format($bc_time / $i * 1000, 1) . ' ms / item'; 556 | $btm = number_format($bc_max_time * 1000, 1) . ' ms'; 557 | 558 | echo "$i: report\n"; 559 | echo "$errors_count errors\n"; 560 | echo "$warnings_count get_browser() errors fixed\n"; 561 | echo "lib time: $lt ($ltpp, max $ltm)\n"; 562 | echo "bc time: $bt ($btpp, max $btm)\n"; 563 | echo "\n"; 564 | } 565 | 566 | protected function checkProperties() 567 | { 568 | $lib_properties = get_object_vars(get_browser('x')); 569 | 570 | $bc_properties = get_object_vars($this->browscap->getBrowser('x')); 571 | 572 | foreach (array_keys($bc_properties) as $bc_prop) 573 | { 574 | if ('browser_name' == $bc_prop) 575 | { 576 | continue; 577 | } 578 | 579 | if (!isset($lib_properties[strtolower($bc_prop)])) 580 | { 581 | throw new Exception("Property `$bc_prop` from Browscap doesn't match anything in get_browser."); 582 | } 583 | 584 | if ('browser_name_regex' != $bc_prop) 585 | { 586 | $this->properties[$bc_prop] = strtolower($bc_prop); 587 | } 588 | 589 | unset($lib_properties[strtolower($bc_prop)]); 590 | } 591 | 592 | unset($lib_properties['renderingengine_description']); 593 | 594 | if (!empty($lib_properties)) 595 | { 596 | throw new Exception('There are ' . count($lib_properties) . '(' . implode(', ', array_keys($lib_properties)) . ') properties in get_browser that do not match those in Browscap.'); 597 | } 598 | } 599 | 600 | protected function getUserAgents() 601 | { 602 | if (empty($this->browscap_ini_path)) 603 | { 604 | throw new Exception("You have to have php.ini 'browscap' directive set to run this test."); 605 | } 606 | 607 | if (!is_file($this->browscap_ini_path)) 608 | { 609 | throw new Exception("There is no browscap file at {$this->browscap_ini_path} location."); 610 | } 611 | 612 | if (version_compare(PHP_VERSION, '5.3.0', '>=')) 613 | { 614 | $browscap_data = parse_ini_file($this->browscap_ini_path, true, INI_SCANNER_RAW); 615 | } 616 | else 617 | { 618 | $browscap_data = parse_ini_file($this->browscap_ini_path, true); 619 | } 620 | 621 | $browscap_data = array_keys($browscap_data); 622 | 623 | $this->user_agents = explode("\n", file_get_contents('user-agent-examples.txt')); 624 | 625 | $this->user_agents[] = uniqid('Fake User Agent ', true); 626 | 627 | foreach ($browscap_data as $pattern) 628 | { 629 | $this->user_agents[] = str_replace(array('?', '*'), array('Z', 'XY'), $pattern); 630 | 631 | if (false !== strpos($pattern, '*')) 632 | { 633 | $this->user_agents[] = str_replace(array('?', '*'), array('Z', ''), $pattern); 634 | } 635 | } 636 | 637 | echo number_format(count($this->user_agents)) . " possible user agents\n"; 638 | } 639 | } 640 | 641 | -------------------------------------------------------------------------------- /tests/compare/results.txt: -------------------------------------------------------------------------------- 1 | 13,121 possible user agents 2 | 3 | get_browser() error fixed for 'Mozilla/5.0 (compatible; MSIE 7.0; MSIE 6.0; ScanAlert; +http://www.scanalert.com/bot.jsp) Firefox/2.0.0.3' 4 | 5 | get_browser() error fixed for 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)' 6 | 7 | get_browser() error fixed for 'Mozilla/5.0 (compatible; AhrefsBot/4.0; +http://ahrefs.com/robot/)' 8 | 9 | 500: report 10 | 0 errors 11 | 3 get_browser() errors fixed 12 | lib time: 21.17 sec (42.3 ms / item, max 58.7 ms) 13 | bc time: 3.91 sec (7.8 ms / item, max 21.9 ms) 14 | 15 | 1000: report 16 | 0 errors 17 | 3 get_browser() errors fixed 18 | lib time: 45.14 sec (45.1 ms / item, max 58.7 ms) 19 | bc time: 4.56 sec (4.6 ms / item, max 21.9 ms) 20 | 21 | 1500: report 22 | 0 errors 23 | 3 get_browser() errors fixed 24 | lib time: 67.32 sec (44.9 ms / item, max 58.7 ms) 25 | bc time: 6.80 sec (4.5 ms / item, max 21.9 ms) 26 | 27 | 2000: report 28 | 0 errors 29 | 3 get_browser() errors fixed 30 | lib time: 90.36 sec (45.2 ms / item, max 58.7 ms) 31 | bc time: 8.18 sec (4.1 ms / item, max 21.9 ms) 32 | 33 | 2500: report 34 | 0 errors 35 | 3 get_browser() errors fixed 36 | lib time: 110.83 sec (44.3 ms / item, max 58.7 ms) 37 | bc time: 12.38 sec (5.0 ms / item, max 21.9 ms) 38 | 39 | get_browser() error fixed for 'DefaultProperties' 40 | 41 | get_browser() error fixed for 'Ask' 42 | 43 | get_browser() error fixed for 'Baidu' 44 | 45 | get_browser() error fixed for 'Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)' 46 | 47 | get_browser() error fixed for 'Google' 48 | 49 | get_browser() error fixed for 'MSN' 50 | 51 | get_browser() error fixed for 'msnbot-NewsBlogs/2.XY (+http://search.msn.com/msnbot.htm)' 52 | 53 | get_browser() error fixed for 'msnbot-NewsBlogs/2. (+http://search.msn.com/msnbot.htm)' 54 | 55 | get_browser() error fixed for 'Yahoo' 56 | 57 | get_browser() error fixed for 'Yandex' 58 | 59 | get_browser() error fixed for 'Best of the Web' 60 | 61 | get_browser() error fixed for 'Boitho' 62 | 63 | get_browser() error fixed for 'Convera' 64 | 65 | get_browser() error fixed for 'DotBot' 66 | 67 | get_browser() error fixed for 'Entireweb' 68 | 69 | get_browser() error fixed for 'Envolk' 70 | 71 | get_browser() error fixed for 'Exalead' 72 | 73 | get_browser() error fixed for 'Facebook' 74 | 75 | get_browser() error fixed for 'facebookexternalhit/1.0 (+httpXY://www.facebook.com/externalhit_uatext.php)XY' 76 | 77 | get_browser() error fixed for 'facebookexternalhit/1.0 (+http://www.facebook.com/externalhit_uatext.php)' 78 | 79 | get_browser() error fixed for 'facebookexternalhit/1.1 (+httpXY://www.facebook.com/externalhit_uatext.php)XY' 80 | 81 | get_browser() error fixed for 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)' 82 | 83 | get_browser() error fixed for 'Fast/AllTheWeb' 84 | 85 | get_browser() error fixed for 'Gigabot' 86 | 87 | get_browser() error fixed for 'Gigabot' 88 | 89 | get_browser() error fixed for 'Ilse' 90 | 91 | get_browser() error fixed for 'iVia Project' 92 | 93 | get_browser() error fixed for 'Jayde Online' 94 | 95 | get_browser() error fixed for 'Lycos' 96 | 97 | get_browser() error fixed for 'Lycos' 98 | 99 | get_browser() error fixed for 'Snap' 100 | 101 | get_browser() error fixed for 'Sogou' 102 | 103 | get_browser() error fixed for 'YodaoBot' 104 | 105 | get_browser() error fixed for 'General Crawlers' 106 | 107 | 3000: report 108 | 0 errors 109 | 37 get_browser() errors fixed 110 | lib time: 128.25 sec (42.7 ms / item, max 58.7 ms) 111 | bc time: 16.50 sec (5.5 ms / item, max 21.9 ms) 112 | 113 | get_browser() error fixed for 'Mozilla/5.0 (compatible; aiHitBotXY/XY; +http://www.aihit.com/)' 114 | 115 | get_browser() error fixed for 'Mozilla/5.0 (compatible; aiHitBot/; +http://www.aihit.com/)' 116 | 117 | get_browser() error fixed for 'Mozilla/5.0 (compatible; Crawly/1.XY; +http://XY/crawler.html)' 118 | 119 | get_browser() error fixed for 'Mozilla/5.0 (compatible; Crawly/1.; +http:///crawler.html)' 120 | 121 | get_browser() error fixed for 'Mozilla/5.0 (compatible; Diffbot/0.1; +http://www.diffbot.com)' 122 | 123 | get_browser() error fixed for 'Mozilla/5.0 (compatible; MSIE 7.0; MSIE 6.0; ScanAlert; +http://www.scanalert.com/bot.jsp) Firefox/XY' 124 | 125 | get_browser() error fixed for 'Mozilla/5.0 (compatible; MSIE 7.0; MSIE 6.0; ScanAlert; +http://www.scanalert.com/bot.jsp) Firefox/' 126 | 127 | get_browser() error fixed for 'Mozilla/5.0 (compatible; SuchbaerBot/0.XY; +http://bot.suchbaer.de/info.html)' 128 | 129 | get_browser() error fixed for 'Mozilla/5.0 (compatible; SuchbaerBot/0.; +http://bot.suchbaer.de/info.html)' 130 | 131 | get_browser() error fixed for 'Mozilla/5.0 (compatible; TweetedTimes Bot/1.0; +http://tweetedtimes.com)' 132 | 133 | get_browser() error fixed for 'Mozilla/5.0 (compatible; unwrapbot/2.XY; +http://www.unwrap.jpXY)' 134 | 135 | get_browser() error fixed for 'Mozilla/5.0 (compatible; unwrapbot/2.; +http://www.unwrap.jp)' 136 | 137 | get_browser() error fixed for 'Search Engines' 138 | 139 | get_browser() error fixed for 'CatchBot/XY; +http://www.catchbot.com' 140 | 141 | get_browser() error fixed for 'CatchBot/; +http://www.catchbot.com' 142 | 143 | get_browser() error fixed for 'Mozilla/5.0 (compatible; AhrefsBot/XY; +http://ahrefs.com/robot/)' 144 | 145 | get_browser() error fixed for 'Mozilla/5.0 (compatible; AhrefsBot/; +http://ahrefs.com/robot/)' 146 | 147 | get_browser() error fixed for 'Mozilla/5.0 (compatible; ScoutJet; +http://www.scoutjet.com/)' 148 | 149 | get_browser() error fixed for 'Mozilla/5.0 (compatible; Scrubby/XY; +http://www.scrubtheweb.com/abs/meta-check.html)' 150 | 151 | get_browser() error fixed for 'Mozilla/5.0 (compatible; Scrubby/; +http://www.scrubtheweb.com/abs/meta-check.html)' 152 | 153 | get_browser() error fixed for 'SosospiderZ(+http://help.soso.com/webspider.htm)' 154 | 155 | get_browser() error fixed for 'BitTorrent Clients' 156 | 157 | get_browser() error fixed for 'Hatena' 158 | 159 | get_browser() error fixed for 'Internet Archive' 160 | 161 | 3500: report 162 | 0 errors 163 | 61 get_browser() errors fixed 164 | lib time: 145.53 sec (41.6 ms / item, max 58.7 ms) 165 | bc time: 20.76 sec (5.9 ms / item, max 21.9 ms) 166 | 167 | get_browser() error fixed for 'Nutch' 168 | 169 | get_browser() error fixed for 'Nutch' 170 | 171 | get_browser() error fixed for 'Webaroo' 172 | 173 | get_browser() error fixed for 'Word Press' 174 | 175 | get_browser() error fixed for 'Copyright/Plagiarism' 176 | 177 | get_browser() error fixed for 'DNS Tools' 178 | 179 | get_browser() error fixed for 'Download Managers' 180 | 181 | get_browser() error fixed for 'E-Mail Harvesters' 182 | 183 | get_browser() error fixed for 'Feeds Blogs' 184 | 185 | get_browser() error fixed for 'Feeds Syndicators' 186 | 187 | get_browser() error fixed for 'General RSS' 188 | 189 | get_browser() error fixed for 'UniversalFeedParser/4.XY +http://feedparser.org/' 190 | 191 | get_browser() error fixed for 'UniversalFeedParser/4. +http://feedparser.org/' 192 | 193 | get_browser() error fixed for 'HTML Validators' 194 | 195 | get_browser() error fixed for 'Image Crawlers' 196 | 197 | get_browser() error fixed for 'Link Checkers' 198 | 199 | get_browser() error fixed for 'Microsoft' 200 | 201 | 4000: report 202 | 0 errors 203 | 78 get_browser() errors fixed 204 | lib time: 163.10 sec (40.8 ms / item, max 58.7 ms) 205 | bc time: 25.28 sec (6.3 ms / item, max 21.9 ms) 206 | 207 | get_browser() error fixed for 'Miscellaneous Browsers' 208 | 209 | get_browser() error fixed for 'Offline Browsers' 210 | 211 | get_browser() error fixed for 'Online Scanners' 212 | 213 | get_browser() error fixed for 'Proxy Servers' 214 | 215 | get_browser() error fixed for 'Research Projects' 216 | 217 | get_browser() error fixed for 'Mozilla/5.0 (compatible; Webscan v0.XY; +http://otc.dyndns.org/webscan/)' 218 | 219 | get_browser() error fixed for 'Mozilla/5.0 (compatible; Webscan v0.; +http://otc.dyndns.org/webscan/)' 220 | 221 | get_browser() error fixed for 'Rippers' 222 | 223 | get_browser() error fixed for 'Site Monitors' 224 | 225 | get_browser() error fixed for 'Mozilla/5.0 (compatible; Chirp/1.0; +http://www.binarycanary.com/chirp.cfm)' 226 | 227 | get_browser() error fixed for 'Social Networking' 228 | 229 | get_browser() error fixed for 'Mozilla/5.0 (compatible; FriendFeedBot/0.XY; +Http://friendfeed.com/about/bot)' 230 | 231 | get_browser() error fixed for 'Mozilla/5.0 (compatible; FriendFeedBot/0.; +Http://friendfeed.com/about/bot)' 232 | 233 | get_browser() error fixed for 'Mozilla/5.0 (compatible; Twitturls; +http://twitturls.com)' 234 | 235 | get_browser() error fixed for 'Translators' 236 | 237 | get_browser() error fixed for 'Version Checkers' 238 | 239 | get_browser() error fixed for 'Automated Browscap.ini Updater. To report issues contact us at+http://www.skycomp.ca' 240 | 241 | 4500: report 242 | 0 errors 243 | 95 get_browser() errors fixed 244 | lib time: 180.79 sec (40.2 ms / item, max 58.7 ms) 245 | bc time: 30.06 sec (6.7 ms / item, max 21.9 ms) 246 | 247 | get_browser() error fixed for 'W3C' 248 | 249 | get_browser() error fixed for 'Become' 250 | 251 | get_browser() error fixed for 'Blue Coat Systems' 252 | 253 | get_browser() error fixed for 'FeedHub' 254 | 255 | get_browser() error fixed for 'Internet Content Rating Association' 256 | 257 | get_browser() error fixed for 'Nagios' 258 | 259 | get_browser() error fixed for 'NameProtect' 260 | 261 | get_browser() error fixed for 'Netcraft' 262 | 263 | get_browser() error fixed for 'NewsGator' 264 | 265 | get_browser() error fixed for 'Chromium 10.0' 266 | 267 | get_browser() error fixed for 'Chromium 11.0' 268 | 269 | get_browser() error fixed for 'Chromium 12.0' 270 | 271 | get_browser() error fixed for 'Chromium 13.0' 272 | 273 | get_browser() error fixed for 'Chromium 14.0' 274 | 275 | get_browser() error fixed for 'Chromium 15.0' 276 | 277 | get_browser() error fixed for 'Chromium 16.0' 278 | 279 | get_browser() error fixed for 'Chromium 17.0' 280 | 281 | get_browser() error fixed for 'Chromium 18.0' 282 | 283 | get_browser() error fixed for 'Chromium 19.0' 284 | 285 | get_browser() error fixed for 'Chromium 20.0' 286 | 287 | get_browser() error fixed for 'Chromium 21.0' 288 | 289 | get_browser() error fixed for 'Chromium 22.0' 290 | 291 | get_browser() error fixed for 'Chromium 23.0' 292 | 293 | get_browser() error fixed for 'Chromium 24.0' 294 | 295 | get_browser() error fixed for 'Chromium 25.0' 296 | 297 | get_browser() error fixed for 'Chromium 26.0' 298 | 299 | get_browser() error fixed for 'Chromium 27.0' 300 | 301 | get_browser() error fixed for 'Chromium 28.0' 302 | 303 | get_browser() error fixed for 'Chromium 29.0' 304 | 305 | get_browser() error fixed for 'Chromium 30.0' 306 | 307 | get_browser() error fixed for 'Chromium 31.0' 308 | 309 | get_browser() error fixed for 'Chromium 32.0' 310 | 311 | get_browser() error fixed for 'Chromium 6.0' 312 | 313 | get_browser() error fixed for 'Chromium 7.0' 314 | 315 | get_browser() error fixed for 'Chromium 8.0' 316 | 317 | get_browser() error fixed for 'Chromium 9.0' 318 | 319 | get_browser() error fixed for 'Chromium Generic' 320 | 321 | get_browser() error fixed for 'Chrome 10.0' 322 | 323 | get_browser() error fixed for 'Chrome 11.0' 324 | 325 | get_browser() error fixed for 'Chrome 12.0' 326 | 327 | get_browser() error fixed for 'Chrome 13.0' 328 | 329 | get_browser() error fixed for 'Chrome 14.0' 330 | 331 | get_browser() error fixed for 'Chrome 15.0' 332 | 333 | get_browser() error fixed for 'Chrome 16.0' 334 | 335 | get_browser() error fixed for 'Chrome 17.0' 336 | 337 | get_browser() error fixed for 'Chrome 18.0' 338 | 339 | 5000: report 340 | 0 errors 341 | 141 get_browser() errors fixed 342 | lib time: 200.79 sec (40.2 ms / item, max 58.7 ms) 343 | bc time: 31.64 sec (6.3 ms / item, max 21.9 ms) 344 | 345 | get_browser() error fixed for 'Chrome 19.0' 346 | 347 | get_browser() error fixed for 'Chrome 20.0' 348 | 349 | get_browser() error fixed for 'Chrome 21.0' 350 | 351 | get_browser() error fixed for 'Chrome 22.0' 352 | 353 | get_browser() error fixed for 'Chrome 23.0' 354 | 355 | get_browser() error fixed for 'Chrome 24.0' 356 | 357 | get_browser() error fixed for 'Chrome 25.0' 358 | 359 | get_browser() error fixed for 'Chrome 26.0' 360 | 361 | get_browser() error fixed for 'Chrome 27.0' 362 | 363 | get_browser() error fixed for 'Chrome 28.0' 364 | 365 | 5500: report 366 | 0 errors 367 | 151 get_browser() errors fixed 368 | lib time: 222.82 sec (40.5 ms / item, max 60.1 ms) 369 | bc time: 32.27 sec (5.9 ms / item, max 21.9 ms) 370 | 371 | get_browser() error fixed for 'Chrome 29.0' 372 | 373 | get_browser() error fixed for 'Chrome 30.0' 374 | 375 | get_browser() error fixed for 'Chrome 31.0' 376 | 377 | get_browser() error fixed for 'Chrome 32.0' 378 | 379 | get_browser() error fixed for 'Chrome 6.0' 380 | 381 | get_browser() error fixed for 'Chrome 7.0' 382 | 383 | get_browser() error fixed for 'Chrome 8.0' 384 | 385 | get_browser() error fixed for 'Chrome 9.0' 386 | 387 | get_browser() error fixed for 'Chrome Generic' 388 | 389 | get_browser() error fixed for 'Google Code' 390 | 391 | get_browser() error fixed for 'Iron 10.0' 392 | 393 | get_browser() error fixed for 'Iron 11.0' 394 | 395 | get_browser() error fixed for 'Iron 12.0' 396 | 397 | get_browser() error fixed for 'Iron 13.0' 398 | 399 | get_browser() error fixed for 'Iron 14.0' 400 | 401 | get_browser() error fixed for 'Iron 15.0' 402 | 403 | get_browser() error fixed for 'Iron 16.0' 404 | 405 | 6000: report 406 | 0 errors 407 | 168 get_browser() errors fixed 408 | lib time: 244.59 sec (40.8 ms / item, max 60.1 ms) 409 | bc time: 33.02 sec (5.5 ms / item, max 21.9 ms) 410 | 411 | get_browser() error fixed for 'Iron 17.0' 412 | 413 | get_browser() error fixed for 'Iron 18.0' 414 | 415 | get_browser() error fixed for 'Iron 19.0' 416 | 417 | get_browser() error fixed for 'Iron 20.0' 418 | 419 | get_browser() error fixed for 'Iron 21.0' 420 | 421 | get_browser() error fixed for 'Iron 22.0' 422 | 423 | get_browser() error fixed for 'Iron 23.0' 424 | 425 | get_browser() error fixed for 'Iron 24.0' 426 | 427 | get_browser() error fixed for 'Iron 25.0' 428 | 429 | get_browser() error fixed for 'Iron 26.0' 430 | 431 | get_browser() error fixed for 'Iron 27.0' 432 | 433 | get_browser() error fixed for 'Iron 28.0' 434 | 435 | get_browser() error fixed for 'Iron 29.0' 436 | 437 | get_browser() error fixed for 'Iron 30.0' 438 | 439 | get_browser() error fixed for 'Iron 31.0' 440 | 441 | get_browser() error fixed for 'Iron 32.0' 442 | 443 | get_browser() error fixed for 'Iron 6.0' 444 | 445 | get_browser() error fixed for 'Iron 7.0' 446 | 447 | get_browser() error fixed for 'Iron 8.0' 448 | 449 | get_browser() error fixed for 'Iron 9.0' 450 | 451 | get_browser() error fixed for 'Iron Generic' 452 | 453 | get_browser() error fixed for 'Rockmelt' 454 | 455 | get_browser() error fixed for 'Arora 0.10' 456 | 457 | get_browser() error fixed for 'Arora 0.11' 458 | 459 | get_browser() error fixed for 'Arora 0.8' 460 | 461 | get_browser() error fixed for 'Arora 0.9' 462 | 463 | get_browser() error fixed for 'Arora Generic' 464 | 465 | get_browser() error fixed for 'Media Players' 466 | 467 | get_browser() error fixed for 'Microsoft Zune' 468 | 469 | get_browser() error fixed for 'Nintendo Wii' 470 | 471 | get_browser() error fixed for 'Windows Media Player' 472 | 473 | get_browser() error fixed for 'QuickTime 10.0' 474 | 475 | 6500: report 476 | 0 errors 477 | 200 get_browser() errors fixed 478 | lib time: 265.25 sec (40.8 ms / item, max 60.6 ms) 479 | bc time: 34.49 sec (5.3 ms / item, max 21.9 ms) 480 | 481 | get_browser() error fixed for 'QuickTime 5.0' 482 | 483 | get_browser() error fixed for 'QuickTime 6.0' 484 | 485 | get_browser() error fixed for 'QuickTime 7.0' 486 | 487 | get_browser() error fixed for 'QuickTime 7.6' 488 | 489 | get_browser() error fixed for 'Lotus Notes 5.0' 490 | 491 | get_browser() error fixed for 'Lotus Notes 6.0' 492 | 493 | get_browser() error fixed for 'Microsoft Outlook 2007' 494 | 495 | get_browser() error fixed for 'Microsoft Outlook 2010' 496 | 497 | get_browser() error fixed for 'Windows Live Mail' 498 | 499 | get_browser() error fixed for 'Blazer' 500 | 501 | get_browser() error fixed for 'Brew' 502 | 503 | get_browser() error fixed for 'DoCoMo' 504 | 505 | get_browser() error fixed for 'Dolfin' 506 | 507 | get_browser() error fixed for 'IEMobile' 508 | 509 | get_browser() error fixed for 'Jasmine' 510 | 511 | get_browser() error fixed for 'KDDI' 512 | 513 | get_browser() error fixed for 'Kindle' 514 | 515 | get_browser() error fixed for 'Maemo' 516 | 517 | get_browser() error fixed for 'Motorola Internet Browser' 518 | 519 | get_browser() error fixed for 'Nokia' 520 | 521 | get_browser() error fixed for 'Openwave Mobile Browser' 522 | 523 | get_browser() error fixed for 'Palm Web' 524 | 525 | get_browser() error fixed for 'Playstation' 526 | 527 | get_browser() error fixed for 'Pocket PC' 528 | 529 | get_browser() error fixed for 'Polaris' 530 | 531 | get_browser() error fixed for 'SEMC Browser' 532 | 533 | get_browser() error fixed for 'Silk' 534 | 535 | get_browser() error fixed for 'Skyfire' 536 | 537 | get_browser() error fixed for 'Teleca' 538 | 539 | get_browser() error fixed for 'UC Browser' 540 | 541 | get_browser() error fixed for 'Android Browser 3.0' 542 | 543 | get_browser() error fixed for 'Android Browser 4.0' 544 | 545 | get_browser() error fixed for 'BlackBerry' 546 | 547 | get_browser() error fixed for 'BlackBerry' 548 | 549 | get_browser() error fixed for 'Mobile Safari 3.0' 550 | 551 | get_browser() error fixed for 'Mobile Safari 3.1' 552 | 553 | get_browser() error fixed for 'Mobile Safari 4.0' 554 | 555 | get_browser() error fixed for 'Mobile Safari 5.0' 556 | 557 | 7000: report 558 | 0 errors 559 | 238 get_browser() errors fixed 560 | lib time: 285.30 sec (40.8 ms / item, max 60.6 ms) 561 | bc time: 36.72 sec (5.2 ms / item, max 21.9 ms) 562 | 563 | get_browser() error fixed for 'Mobile Safari 5.1' 564 | 565 | get_browser() error fixed for 'Mobile Safari 6.0' 566 | 567 | get_browser() error fixed for 'Mobile Safari 6.1' 568 | 569 | get_browser() error fixed for 'Mobile Safari 7.0' 570 | 571 | get_browser() error fixed for 'Opera Mini 2.0' 572 | 573 | get_browser() error fixed for 'Opera Mini 3.0' 574 | 575 | get_browser() error fixed for 'Opera Mini 4.0' 576 | 577 | get_browser() error fixed for 'Opera Mini 5.0' 578 | 579 | get_browser() error fixed for 'Opera Mini 6.0' 580 | 581 | get_browser() error fixed for 'Opera Mini 7.0' 582 | 583 | get_browser() error fixed for 'Opera Mini 8.0' 584 | 585 | get_browser() error fixed for 'Opera Mini 9.0' 586 | 587 | get_browser() error fixed for 'Opera Mini Generic' 588 | 589 | get_browser() error fixed for 'Opera Mobile' 590 | 591 | get_browser() error fixed for 'NetFront 2.0' 592 | 593 | get_browser() error fixed for 'NetFront 3.0' 594 | 595 | get_browser() error fixed for 'Boxee' 596 | 597 | get_browser() error fixed for 'GoogleTV' 598 | 599 | get_browser() error fixed for 'Netbox' 600 | 601 | get_browser() error fixed for 'PowerTV' 602 | 603 | get_browser() error fixed for 'WebTV' 604 | 605 | get_browser() error fixed for 'Amaya' 606 | 607 | get_browser() error fixed for 'Links' 608 | 609 | get_browser() error fixed for 'Lynx' 610 | 611 | get_browser() error fixed for 'Mosaic' 612 | 613 | get_browser() error fixed for 'w3m' 614 | 615 | get_browser() error fixed for 'ELinks 0.10' 616 | 617 | get_browser() error fixed for 'ELinks 0.11' 618 | 619 | 7500: report 620 | 0 errors 621 | 266 get_browser() errors fixed 622 | lib time: 305.32 sec (40.7 ms / item, max 60.6 ms) 623 | bc time: 40.03 sec (5.3 ms / item, max 21.9 ms) 624 | 625 | get_browser() error fixed for 'ELinks 0.12' 626 | 627 | get_browser() error fixed for 'ELinks 0.13' 628 | 629 | get_browser() error fixed for 'ELinks 0.9' 630 | 631 | get_browser() error fixed for 'Camino' 632 | 633 | get_browser() error fixed for 'Chimera' 634 | 635 | get_browser() error fixed for 'Dillo' 636 | 637 | get_browser() error fixed for 'Emacs/W3' 638 | 639 | get_browser() error fixed for 'fantomas' 640 | 641 | get_browser() error fixed for 'FrontPage' 642 | 643 | get_browser() error fixed for 'Galeon' 644 | 645 | get_browser() error fixed for 'HP Secure Web Browser' 646 | 647 | get_browser() error fixed for 'IBrowse' 648 | 649 | get_browser() error fixed for 'iCab' 650 | 651 | get_browser() error fixed for 'iSiloX' 652 | 653 | get_browser() error fixed for 'Lycoris Desktop/LX' 654 | 655 | get_browser() error fixed for 'NetPositive' 656 | 657 | get_browser() error fixed for 'Shiira' 658 | 659 | get_browser() error fixed for 'K-Meleon 1.0' 660 | 661 | get_browser() error fixed for 'K-Meleon 1.1' 662 | 663 | get_browser() error fixed for 'K-Meleon 1.5' 664 | 665 | get_browser() error fixed for 'K-Meleon 1.6' 666 | 667 | get_browser() error fixed for 'Konqueror 3.0' 668 | 669 | get_browser() error fixed for 'Konqueror 4.0' 670 | 671 | get_browser() error fixed for 'Konqueror 4.5' 672 | 673 | get_browser() error fixed for 'Konqueror 4.6' 674 | 675 | 8000: report 676 | 0 errors 677 | 291 get_browser() errors fixed 678 | lib time: 325.06 sec (40.6 ms / item, max 60.6 ms) 679 | bc time: 43.72 sec (5.5 ms / item, max 21.9 ms) 680 | 681 | get_browser() error fixed for 'Konqueror 4.7' 682 | 683 | get_browser() error fixed for 'Konqueror 4.8' 684 | 685 | get_browser() error fixed for 'Safari 2.0' 686 | 687 | get_browser() error fixed for 'Safari 3.0' 688 | 689 | get_browser() error fixed for 'Safari 4.0' 690 | 691 | get_browser() error fixed for 'Safari 5.0' 692 | 693 | get_browser() error fixed for 'Safari 5.1' 694 | 695 | get_browser() error fixed for 'Safari 6.0' 696 | 697 | get_browser() error fixed for 'Safari 6.1' 698 | 699 | get_browser() error fixed for 'Safari 7.0' 700 | 701 | get_browser() error fixed for 'Safari Generic' 702 | 703 | get_browser() error fixed for 'Lunascape 5.0' 704 | 705 | get_browser() error fixed for 'Lunascape 5.1' 706 | 707 | get_browser() error fixed for 'Lunascape 6.0' 708 | 709 | get_browser() error fixed for 'Maxthon 2.0' 710 | 711 | get_browser() error fixed for 'Maxthon 3.0' 712 | 713 | get_browser() error fixed for 'OmniWeb 5.0' 714 | 715 | get_browser() error fixed for 'OmniWeb 5.10' 716 | 717 | get_browser() error fixed for 'OmniWeb 5.11' 718 | 719 | get_browser() error fixed for 'Opera 10.00' 720 | 721 | get_browser() error fixed for 'Opera 11.00' 722 | 723 | 8500: report 724 | 0 errors 725 | 312 get_browser() errors fixed 726 | lib time: 346.10 sec (40.7 ms / item, max 60.6 ms) 727 | bc time: 45.81 sec (5.4 ms / item, max 21.9 ms) 728 | 729 | get_browser() error fixed for 'Opera 11.10' 730 | 731 | get_browser() error fixed for 'Opera 11.50' 732 | 733 | get_browser() error fixed for 'Opera 11.60' 734 | 735 | get_browser() error fixed for 'Opera 12.00' 736 | 737 | 9000: report 738 | 0 errors 739 | 316 get_browser() errors fixed 740 | lib time: 366.54 sec (40.7 ms / item, max 60.6 ms) 741 | bc time: 49.31 sec (5.5 ms / item, max 21.9 ms) 742 | 743 | get_browser() error fixed for 'Opera 12.10' 744 | 745 | get_browser() error fixed for 'Opera 12.11' 746 | 747 | get_browser() error fixed for 'Opera 12.12' 748 | 749 | get_browser() error fixed for 'Opera 12.13' 750 | 751 | 9500: report 752 | 0 errors 753 | 320 get_browser() errors fixed 754 | lib time: 386.57 sec (40.7 ms / item, max 60.6 ms) 755 | bc time: 52.61 sec (5.5 ms / item, max 21.9 ms) 756 | 757 | get_browser() error fixed for 'Opera 12.14' 758 | 759 | get_browser() error fixed for 'Opera 12.15' 760 | 761 | get_browser() error fixed for 'Opera 12.16' 762 | 763 | get_browser() error fixed for 'Opera 2.00' 764 | 765 | get_browser() error fixed for 'Opera 3.00' 766 | 767 | 10000: report 768 | 0 errors 769 | 325 get_browser() errors fixed 770 | lib time: 406.81 sec (40.7 ms / item, max 60.6 ms) 771 | bc time: 56.04 sec (5.6 ms / item, max 21.9 ms) 772 | 773 | get_browser() error fixed for 'Opera 4.00' 774 | 775 | get_browser() error fixed for 'Opera 5.00' 776 | 777 | get_browser() error fixed for 'Opera 6.00' 778 | 779 | get_browser() error fixed for 'Opera 7.00' 780 | 781 | get_browser() error fixed for 'Opera 8.00' 782 | 783 | 10500: report 784 | 0 errors 785 | 330 get_browser() errors fixed 786 | lib time: 426.81 sec (40.6 ms / item, max 60.6 ms) 787 | bc time: 59.79 sec (5.7 ms / item, max 21.9 ms) 788 | 789 | get_browser() error fixed for 'Opera 9.00' 790 | 791 | get_browser() error fixed for 'Opera Generic' 792 | 793 | get_browser() error fixed for 'Netscape 4.0' 794 | 795 | get_browser() error fixed for 'Netscape 4.7' 796 | 797 | get_browser() error fixed for 'Netscape 4.8' 798 | 799 | get_browser() error fixed for 'Netscape 6.0' 800 | 801 | get_browser() error fixed for 'Netscape 7.0' 802 | 803 | get_browser() error fixed for 'Netscape 8.0' 804 | 805 | get_browser() error fixed for 'Netscape 9.0' 806 | 807 | get_browser() error fixed for 'Palemoon' 808 | 809 | get_browser() error fixed for 'SeaMonkey 1.0' 810 | 811 | 11000: report 812 | 0 errors 813 | 341 get_browser() errors fixed 814 | lib time: 447.09 sec (40.6 ms / item, max 60.6 ms) 815 | bc time: 63.08 sec (5.7 ms / item, max 21.9 ms) 816 | 817 | get_browser() error fixed for 'SeaMonkey 1.1' 818 | 819 | get_browser() error fixed for 'SeaMonkey 2.0' 820 | 821 | get_browser() error fixed for 'SeaMonkey 2.1' 822 | 823 | get_browser() error fixed for 'Seamonkey 2.2' 824 | 825 | get_browser() error fixed for 'Seamonkey 2.3' 826 | 827 | get_browser() error fixed for 'Seamonkey 2.4' 828 | 829 | get_browser() error fixed for 'Seamonkey 2.5' 830 | 831 | get_browser() error fixed for 'Flock 1.0' 832 | 833 | get_browser() error fixed for 'Flock 2.0' 834 | 835 | get_browser() error fixed for 'Flock 3.0' 836 | 837 | get_browser() error fixed for 'Sleipnir' 838 | 839 | get_browser() error fixed for 'Sleipnir' 840 | 841 | get_browser() error fixed for 'Firefox 1.0' 842 | 843 | get_browser() error fixed for 'Firefox 10.0' 844 | 845 | get_browser() error fixed for 'Firefox 11.0' 846 | 847 | get_browser() error fixed for 'Firefox 12.0' 848 | 849 | get_browser() error fixed for 'Firefox 13.0' 850 | 851 | get_browser() error fixed for 'Firefox 14.0' 852 | 853 | get_browser() error fixed for 'Firefox 15.0' 854 | 855 | get_browser() error fixed for 'Firefox 16.0' 856 | 857 | get_browser() error fixed for 'Firefox 17.0' 858 | 859 | get_browser() error fixed for 'Firefox 18.0' 860 | 861 | 11500: report 862 | 0 errors 863 | 363 get_browser() errors fixed 864 | lib time: 468.58 sec (40.7 ms / item, max 60.6 ms) 865 | bc time: 65.36 sec (5.7 ms / item, max 21.9 ms) 866 | 867 | get_browser() error fixed for 'Firefox 19.0' 868 | 869 | get_browser() error fixed for 'Firefox 2.0' 870 | 871 | get_browser() error fixed for 'Firefox 20.0' 872 | 873 | get_browser() error fixed for 'Firefox 21.0' 874 | 875 | get_browser() error fixed for 'Firefox 22.0' 876 | 877 | get_browser() error fixed for 'Firefox 23.0' 878 | 879 | get_browser() error fixed for 'Firefox 24.0' 880 | 881 | get_browser() error fixed for 'Firefox 3.0' 882 | 883 | get_browser() error fixed for 'Firefox 3.1' 884 | 885 | get_browser() error fixed for 'Firefox 3.5' 886 | 887 | get_browser() error fixed for 'Firefox 3.6' 888 | 889 | get_browser() error fixed for 'Firefox 4.0' 890 | 891 | get_browser() error fixed for 'Firefox 4.2' 892 | 893 | 12000: report 894 | 0 errors 895 | 376 get_browser() errors fixed 896 | lib time: 490.19 sec (40.8 ms / item, max 60.6 ms) 897 | bc time: 67.58 sec (5.6 ms / item, max 21.9 ms) 898 | 899 | get_browser() error fixed for 'Firefox 5.0' 900 | 901 | get_browser() error fixed for 'Firefox 6.0' 902 | 903 | get_browser() error fixed for 'Firefox 7.0' 904 | 905 | get_browser() error fixed for 'Firefox 8.0' 906 | 907 | get_browser() error fixed for 'Firefox 9.0' 908 | 909 | get_browser() error fixed for 'Fennec 1.0' 910 | 911 | get_browser() error fixed for 'Fennec 10.0' 912 | 913 | get_browser() error fixed for 'Fennec 4.0' 914 | 915 | get_browser() error fixed for 'Fennec 5.0' 916 | 917 | get_browser() error fixed for 'Fennec 6.0' 918 | 919 | get_browser() error fixed for 'Fennec 7.0' 920 | 921 | get_browser() error fixed for 'Thunderbird 1.0' 922 | 923 | get_browser() error fixed for 'Thunderbird 1.5' 924 | 925 | get_browser() error fixed for 'Thunderbird 10.0' 926 | 927 | get_browser() error fixed for 'Thunderbird 11.0' 928 | 929 | get_browser() error fixed for 'Thunderbird 12.0' 930 | 931 | get_browser() error fixed for 'Thunderbird 13.0' 932 | 933 | get_browser() error fixed for 'Thunderbird 14.0' 934 | 935 | get_browser() error fixed for 'Thunderbird 2.0' 936 | 937 | get_browser() error fixed for 'Thunderbird 3.0' 938 | 939 | get_browser() error fixed for 'Thunderbird 3.1' 940 | 941 | get_browser() error fixed for 'Thunderbird 5.0' 942 | 943 | get_browser() error fixed for 'Thunderbird 6.0' 944 | 945 | 12500: report 946 | 0 errors 947 | 399 get_browser() errors fixed 948 | lib time: 511.21 sec (40.9 ms / item, max 60.6 ms) 949 | bc time: 69.93 sec (5.6 ms / item, max 21.9 ms) 950 | 951 | get_browser() error fixed for 'Thunderbird 7.0' 952 | 953 | get_browser() error fixed for 'Thunderbird 8.0' 954 | 955 | get_browser() error fixed for 'Thunderbird 9.0' 956 | 957 | get_browser() error fixed for 'Iceweasel' 958 | 959 | get_browser() error fixed for 'Mozilla 1.0' 960 | 961 | get_browser() error fixed for 'Mozilla 1.1' 962 | 963 | get_browser() error fixed for 'Mozilla 1.2' 964 | 965 | get_browser() error fixed for 'Mozilla 1.3' 966 | 967 | get_browser() error fixed for 'Mozilla 1.4' 968 | 969 | get_browser() error fixed for 'Mozilla 1.5' 970 | 971 | get_browser() error fixed for 'Mozilla 1.6' 972 | 973 | get_browser() error fixed for 'Mozilla 1.7' 974 | 975 | get_browser() error fixed for 'Mozilla 1.8' 976 | 977 | get_browser() error fixed for 'Mozilla 1.9' 978 | 979 | get_browser() error fixed for 'AOL 9.0/IE 5.5' 980 | 981 | get_browser() error fixed for 'AOL 9.0/IE 6.0' 982 | 983 | get_browser() error fixed for 'AOL 9.0/IE 7.0' 984 | 985 | get_browser() error fixed for 'AOL 9.0/IE 8.0' 986 | 987 | get_browser() error fixed for 'AOL 9.1/IE 7.0' 988 | 989 | get_browser() error fixed for 'AOL 9.1/IE 8.0' 990 | 991 | get_browser() error fixed for 'AOL 9.5' 992 | 993 | get_browser() error fixed for 'AOL 9.6' 994 | 995 | get_browser() error fixed for 'AOL Generic' 996 | 997 | get_browser() error fixed for 'IE 1.0' 998 | 999 | get_browser() error fixed for 'IE 1.5' 1000 | 1001 | get_browser() error fixed for 'IE 10.0' 1002 | 1003 | get_browser() error fixed for 'IE 2.0' 1004 | 1005 | get_browser() error fixed for 'IE 3.0' 1006 | 1007 | get_browser() error fixed for 'IE 4.0' 1008 | 1009 | get_browser() error fixed for 'IE 5.0' 1010 | 1011 | 13000: report 1012 | 0 errors 1013 | 429 get_browser() errors fixed 1014 | lib time: 531.83 sec (40.9 ms / item, max 60.6 ms) 1015 | bc time: 72.53 sec (5.6 ms / item, max 21.9 ms) 1016 | 1017 | get_browser() error fixed for 'IE 6.0' 1018 | 1019 | get_browser() error fixed for 'IE 7.0' 1020 | 1021 | get_browser() error fixed for 'IE 8.0' 1022 | 1023 | get_browser() error fixed for 'IE 9.0' 1024 | 1025 | 13120: report 1026 | 0 errors 1027 | 433 get_browser() errors fixed 1028 | lib time: 536.68 sec (40.9 ms / item, max 60.6 ms) 1029 | bc time: 72.99 sec (5.6 ms / item, max 21.9 ms) 1030 | 1031 | -------------------------------------------------------------------------------- /tests/phpbrowscap/BrowscapTest.php: -------------------------------------------------------------------------------- 1 | 34 | * @copyright Copyright (c) 2006-2012 Jonathan Stoppani 35 | * @version 1.0 36 | * @license http://www.opensource.org/licenses/MIT MIT License 37 | * @link https://github.com/GaretJax/phpbrowscap/ 38 | */ 39 | class BrowscapTest extends TestCase 40 | { 41 | public function testProxyAutoDetection() 42 | { 43 | $browscap = $this->createBrowscap(); 44 | 45 | putenv('http_proxy=http://proxy.example.com:3128'); 46 | putenv('https_proxy=http://proxy.example.com:3128'); 47 | putenv('ftp_proxy=http://proxy.example.com:3128'); 48 | 49 | $browscap->autodetectProxySettings(); 50 | $options = $browscap->getStreamContextOptions(); 51 | 52 | $this->assertEquals($options['http']['proxy'], 'tcp://proxy.example.com:3128'); 53 | $this->assertTrue($options['http']['request_fulluri']); 54 | 55 | $this->assertEquals($options['https']['proxy'], 'tcp://proxy.example.com:3128'); 56 | $this->assertTrue($options['https']['request_fulluri']); 57 | 58 | $this->assertEquals($options['ftp']['proxy'], 'tcp://proxy.example.com:3128'); 59 | $this->assertTrue($options['ftp']['request_fulluri']); 60 | } 61 | 62 | public function testAddProxySettings() 63 | { 64 | $browscap = $this->createBrowscap(); 65 | 66 | $browscap->addProxySettings('proxy.example.com', 3128, 'http'); 67 | $options = $browscap->getStreamContextOptions(); 68 | 69 | $this->assertEquals($options['http']['proxy'], 'tcp://proxy.example.com:3128'); 70 | $this->assertTrue($options['http']['request_fulluri']); 71 | } 72 | 73 | public function testClearProxySettings() 74 | { 75 | $browscap = $this->createBrowscap(); 76 | 77 | $browscap->addProxySettings('proxy.example.com', 3128, 'http'); 78 | $options = $browscap->getStreamContextOptions(); 79 | 80 | $this->assertEquals($options['http']['proxy'], 'tcp://proxy.example.com:3128'); 81 | $this->assertTrue($options['http']['request_fulluri']); 82 | 83 | $clearedWrappers = $browscap->clearProxySettings(); 84 | $options = $browscap->getStreamContextOptions(); 85 | 86 | $this->assertEmpty($options); 87 | $this->assertEquals($clearedWrappers, array('http')); 88 | } 89 | 90 | public function testGetStreamContext() 91 | { 92 | $cacheDir = $this->createCacheDir(); 93 | $browscap = new BrowscapForTest($cacheDir); 94 | 95 | $browscap->addProxySettings('proxy.example.com', 3128, 'http'); 96 | 97 | $resource = $browscap->getStreamContext(); 98 | 99 | $this->assertTrue(is_resource($resource)); 100 | } 101 | } 102 | 103 | class BrowscapForTest extends Browscap 104 | { 105 | public function getStreamContext($recreate = false) 106 | { 107 | return $this->_getStreamContext($recreate); 108 | } 109 | } -------------------------------------------------------------------------------- /tests/phpbrowscap/TestCase.php: -------------------------------------------------------------------------------- 1 | 34 | * @copyright Copyright (c) 2006-2012 Jonathan Stoppani 35 | * @version 1.0 36 | * @license http://www.opensource.org/licenses/MIT MIT License 37 | * @link https://github.com/GaretJax/phpbrowscap/ 38 | */ 39 | class TestCase extends \PHPUnit_Framework_TestCase 40 | { 41 | protected $cacheDir; 42 | 43 | public function setUp() 44 | { 45 | } 46 | 47 | protected function createCacheDir($cache_dir = null) 48 | { 49 | $cacheDir = sys_get_temp_dir().DIRECTORY_SEPARATOR.'browscap_testing'; 50 | 51 | if (!is_dir($cacheDir)) { 52 | if (false === @mkdir($cacheDir, 0777, true)) { 53 | throw new \RuntimeException(sprintf('Unable to create the "%s" directory', $cacheDir)); 54 | } 55 | } 56 | 57 | $this->cacheDir = $cacheDir; 58 | 59 | return $this->cacheDir; 60 | } 61 | 62 | protected function createBrowscap() 63 | { 64 | $cacheDir = $this->createCacheDir(); 65 | 66 | return new Browscap($cacheDir); 67 | } 68 | 69 | protected function removeCacheDir() 70 | { 71 | if (isset($this->cacheDir) && is_dir($this->cacheDir)) { 72 | if (false === @rmdir($this->cacheDir)) { 73 | throw new \RuntimeException(sprintf('Unable to remove the "%s" directory', $this->cacheDir)); 74 | } 75 | 76 | $this->cacheDir = null; 77 | } 78 | } 79 | 80 | public function tearDown() 81 | { 82 | $this->removeCacheDir(); 83 | } 84 | } 85 | --------------------------------------------------------------------------------