PageRenderTime 38ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/no-ie6/vendor/Browscap.php

https://bitbucket.org/seyar/parshin.local
PHP | 663 lines | 340 code | 96 blank | 227 comment | 60 complexity | 28b28398626abe08d82560568aadef6a MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1
  1. <?php
  2. /**
  3. * Browscap.ini parsing class with caching and update capabilities
  4. *
  5. * PHP version 5
  6. *
  7. * LICENSE: This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 2.1 of the License, or (at your option) any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this library; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  20. *
  21. * @package Browscap
  22. * @author Jonathan Stoppani <st.jonathan@gmail.com>
  23. * @copyright Copyright (c) 2006-2008 Jonathan Stoppani
  24. * @version 0.7
  25. * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
  26. * @link http://garetjax.info/projects/browscap/
  27. */
  28. class Browscap
  29. {
  30. /**
  31. * Current version of the class.
  32. */
  33. const VERSION = '0.7';
  34. /**
  35. * Different ways to access remote and local files.
  36. *
  37. * UPDATE_FOPEN: Uses the fopen url wrapper (use file_get_contents).
  38. * UPDATE_FSOCKOPEN: Uses the socket functions (fsockopen).
  39. * UPDATE_CURL: Uses the cURL extension.
  40. * UPDATE_LOCAL: Updates from a local file (file_get_contents).
  41. */
  42. const UPDATE_FOPEN = 'URL-wrapper';
  43. const UPDATE_FSOCKOPEN = 'socket';
  44. const UPDATE_CURL = 'cURL';
  45. const UPDATE_LOCAL = 'local';
  46. /**
  47. * Options for regex patterns.
  48. *
  49. * REGEX_DELIMITER: Delimiter of all the regex patterns in the whole class.
  50. * REGEX_MODIFIERS: Regex modifiers.
  51. */
  52. const REGEX_DELIMITER = '@';
  53. const REGEX_MODIFIERS = 'i';
  54. /**
  55. * The values to quote in the ini file
  56. */
  57. const VALUES_TO_QUOTE = 'Browser|Parent';
  58. /**
  59. * Definitions of the function used by the uasort() function to order the
  60. * userAgents array.
  61. *
  62. * ORDER_FUNC_ARGS: Arguments that the function will take.
  63. * ORDER_FUNC_LOGIC: Internal logic of the function.
  64. */
  65. const ORDER_FUNC_ARGS = '$a, $b';
  66. const ORDER_FUNC_LOGIC = '$a=strlen($a);$b=strlen($b);return$a==$b?0:($a<$b?1:-1);';
  67. /**
  68. * The headers to be sent for checking the version and requesting the file.
  69. */
  70. const REQUEST_HEADERS = "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: %s\r\nConnection: Close\r\n\r\n";
  71. /**
  72. * Options for auto update capabilities
  73. *
  74. * $remoteVerUrl: The location to use to check out if a new version of the
  75. * browscap.ini file is available.
  76. * $remoteIniUrl: The location from which download the ini file.
  77. * The placeholder for the file should be represented by a %s.
  78. * $timeout: The timeout for the requests.
  79. * $updateInterval: The update interval in seconds.
  80. * $errorInterval: The next update interval in seconds in case of an error.
  81. * $doAutoUpdate: Flag to disable the automatic interval based update.
  82. * $updateMethod: The method to use to update the file, has to be a value of
  83. * an UPDATE_* constant, null or false.
  84. */
  85. public $remoteIniUrl = 'http://browsers.garykeith.com/stream.asp?BrowsCapINI';
  86. public $remoteVerUrl = 'http://updates.browserproject.com/version-date.asp';
  87. public $timeout = 5;
  88. public $updateInterval = 432000; // 5 days
  89. public $errorInterval = 7200; // 2 hours
  90. public $doAutoUpdate = true;
  91. public $updateMethod = null;
  92. /**
  93. * The path of the local version of the browscap.ini file from which to
  94. * update (to be set only if used).
  95. *
  96. * @var string
  97. */
  98. public $localFile = null;
  99. /**
  100. * The useragent to include in the requests made by the class during the
  101. * update process.
  102. *
  103. * @var string
  104. */
  105. public $userAgent = 'PHP Browser Capabilities Project/%v %m';
  106. /**
  107. * Flag to enable only lowercase indexes in the result.
  108. * The cache has to be rebuilt in order to apply this option.
  109. *
  110. * @var bool
  111. */
  112. public $lowercase = false;
  113. /**
  114. * Flag to enable/disable silent error management.
  115. * In case of an error during the update process the class returns an empty
  116. * array/object if the update process can't take place and the browscap.ini
  117. * file does not exist.
  118. *
  119. * @var bool
  120. */
  121. public $silent = false;
  122. /**
  123. * Where to store the cached PHP arrays.
  124. *
  125. * @var string
  126. */
  127. public $cacheFilename = 'cache.php';
  128. /**
  129. * Where to store the downloaded ini file.
  130. *
  131. * @var string
  132. */
  133. public $iniFilename = 'browscap.ini';
  134. /**
  135. * Path to the cache directory
  136. *
  137. * @var string
  138. */
  139. public $cacheDir = null;
  140. /**
  141. * Flag to be set to true after loading the cache
  142. *
  143. * @var bool
  144. */
  145. private $_cacheLoaded = false;
  146. /**
  147. * Where to store the value of the included PHP cache file
  148. *
  149. * @var array
  150. */
  151. private $_userAgents = array();
  152. private $_browsers = array();
  153. private $_patterns = array();
  154. private $_properties = array();
  155. /**
  156. * Constructor class, checks for the existence of (and loads) the cache and
  157. * if needed updated the definitions
  158. *
  159. * @param string $cache_dir
  160. */
  161. public function __construct($cache_dir)
  162. {
  163. // has to be set to reach E_STRICT compatibility, does not affect system/app settings
  164. date_default_timezone_set(date_default_timezone_get());
  165. if (!isset($cache_dir)) {
  166. throw new Browscap_Exception(
  167. 'You have to provide a path to read/store the browscap cache file'
  168. );
  169. }
  170. $cache_dir = realpath($cache_dir);
  171. // Is the cache dir really the directory or is it directly the file?
  172. if (substr($cache_dir, -4) === '.php') {
  173. $this->cacheFilename = basename($cache_dir);
  174. $this->cacheDir = dirname($cache_dir);
  175. } else {
  176. $this->cacheDir = $cache_dir;
  177. }
  178. $this->cacheDir .= DIRECTORY_SEPARATOR;
  179. }
  180. /**
  181. * Gets the information about the browser by User Agent
  182. *
  183. * @param string $user_agent the user agent string
  184. * @param bool $return_array whether return an array or an object
  185. * @throws Browscap_Exception
  186. * @return stdObject the object containing the browsers details. Array if
  187. * $return_array is set to true.
  188. */
  189. public function getBrowser($user_agent = null, $return_array = false)
  190. {
  191. // Load the cache at the first request
  192. if (!$this->_cacheLoaded) {
  193. $cache_file = $this->cacheDir . $this->cacheFilename;
  194. $ini_file = $this->cacheDir . $this->iniFilename;
  195. // Set the interval only if needed
  196. if ($this->doAutoUpdate && file_exists($ini_file)) {
  197. $interval = time() - filemtime($ini_file);
  198. } else {
  199. $interval = 0;
  200. }
  201. // Find out if the cache needs to be updated
  202. if (!file_exists($cache_file) || !file_exists($ini_file) || ($interval > $this->updateInterval)) {
  203. try {
  204. $this->updateCache();
  205. } catch (Browscap_Exception $e) {
  206. if (file_exists($ini_file)) {
  207. // Adjust the filemtime to the $errorInterval
  208. touch($ini_file, time() - $this->updateInterval + $this->errorInterval);
  209. } else if ($this->silent) {
  210. // Return an array if silent mode is active and the ini db doesn't exsist
  211. return array();
  212. }
  213. if (!$this->silent) {
  214. throw $e;
  215. }
  216. }
  217. }
  218. $this->_loadCache($cache_file);
  219. }
  220. // Automatically detect the useragent
  221. if (!isset($user_agent)) {
  222. if (isset($_SERVER['HTTP_USER_AGENT'])) {
  223. $user_agent = $_SERVER['HTTP_USER_AGENT'];
  224. } else {
  225. $user_agent = '';
  226. }
  227. }
  228. $browser = array();
  229. foreach ($this->_patterns as $key => $pattern) {
  230. if (preg_match($pattern . 'i', $user_agent)) {
  231. $browser = array(
  232. $user_agent, // Original useragent
  233. trim(strtolower($pattern), self::REGEX_DELIMITER),
  234. $this->_userAgents[$key]
  235. );
  236. $browser = $value = $browser + $this->_browsers[$key];
  237. while (array_key_exists(3, $value) && $value[3]) {
  238. $value = $this->_browsers[$value[3]];
  239. $browser += $value;
  240. }
  241. if (!empty($browser[3])) {
  242. $browser[3] = $this->_userAgents[$browser[3]];
  243. }
  244. break;
  245. }
  246. }
  247. // Add the keys for each property
  248. $array = array();
  249. foreach ($browser as $key => $value) {
  250. $array[$this->_properties[$key]] = $value;
  251. }
  252. return $return_array ? $array : (object) $array;
  253. }
  254. /**
  255. * Parses the ini file and updates the cache files
  256. *
  257. * @return bool whether the file was correctly written to the disk
  258. */
  259. public function updateCache()
  260. {
  261. $ini_path = $this->cacheDir . $this->iniFilename;
  262. $cache_path = $this->cacheDir . $this->cacheFilename;
  263. // Choose the right url
  264. if ($this->_getUpdateMethod() == self::UPDATE_LOCAL) {
  265. $url = $this->localFile;
  266. } else {
  267. $url = $this->remoteIniUrl;
  268. }
  269. $this->_getRemoteIniFile($url, $ini_path);
  270. $browsers = parse_ini_file($ini_path, true);
  271. array_shift($browsers);
  272. $this->_properties = array_keys($browsers['DefaultProperties']);
  273. array_unshift(
  274. $this->_properties,
  275. 'browser_name',
  276. 'browser_name_regex',
  277. 'browser_name_pattern',
  278. 'Parent'
  279. );
  280. $this->_userAgents = array_keys($browsers);
  281. usort(
  282. $this->_userAgents,
  283. create_function(self::ORDER_FUNC_ARGS, self::ORDER_FUNC_LOGIC)
  284. );
  285. $user_agents_keys = array_flip($this->_userAgents);
  286. $properties_keys = array_flip($this->_properties);
  287. $search = array('\*', '\?');
  288. $replace = array('.*', '.');
  289. foreach ($this->_userAgents as $user_agent) {
  290. $pattern = preg_quote($user_agent, self::REGEX_DELIMITER);
  291. $this->_patterns[] = self::REGEX_DELIMITER
  292. . '^'
  293. . str_replace($search, $replace, $pattern)
  294. . '$'
  295. . self::REGEX_DELIMITER;
  296. if (!empty($browsers[$user_agent]['Parent'])) {
  297. $parent = $browsers[$user_agent]['Parent'];
  298. $browsers[$user_agent]['Parent'] = $user_agents_keys[$parent];
  299. }
  300. foreach ($browsers[$user_agent] as $key => $value) {
  301. $key = $properties_keys[$key] . ".0";
  302. $browser[$key] = $value;
  303. }
  304. $this->_browsers[] = $browser;
  305. unset($browser);
  306. }
  307. unset($user_agents_keys, $properties_keys, $browsers);
  308. // Save the keys lowercased if needed
  309. if ($this->lowercase) {
  310. $this->_properties = array_map('strtolower', $this->_properties);
  311. }
  312. // Get the whole PHP code
  313. $cache = $this->_buildCache();
  314. // Save and return
  315. return (bool) file_put_contents($cache_path, $cache, LOCK_EX);
  316. }
  317. /**
  318. * Loads the cache into object's properties
  319. *
  320. * @return void
  321. */
  322. private function _loadCache($cache_file)
  323. {
  324. require $cache_file;
  325. $this->_browsers = $browsers;
  326. $this->_userAgents = $userAgents;
  327. $this->_patterns = $patterns;
  328. $this->_properties = $properties;
  329. $this->_cacheLoaded = true;
  330. }
  331. /**
  332. * Parses the array to cache and creates the PHP string to write to disk
  333. *
  334. * @return string the PHP string to save into the cache file
  335. */
  336. private function _buildCache()
  337. {
  338. $cacheTpl = "<?php\n\$properties=%s;\n\$browsers=%s;\n\$userAgents=%s;\n\$patterns=%s;\n";
  339. $propertiesArray = $this->_array2string($this->_properties);
  340. $patternsArray = $this->_array2string($this->_patterns);
  341. $userAgentsArray = $this->_array2string($this->_userAgents);
  342. $browsersArray = $this->_array2string($this->_browsers);
  343. return sprintf(
  344. $cacheTpl,
  345. $propertiesArray,
  346. $browsersArray,
  347. $userAgentsArray,
  348. $patternsArray
  349. );
  350. }
  351. /**
  352. * Updates the local copy of the ini file (by version checking) and adapts
  353. * his syntax to the PHP ini parser
  354. *
  355. * @param string $url the url of the remote server
  356. * @param string $path the path of the ini file to update
  357. * @throws Browscap_Exception
  358. * @return bool if the ini file was updated
  359. */
  360. private function _getRemoteIniFile($url, $path)
  361. {
  362. // Check version
  363. if (file_exists($path) && filesize($path)) {
  364. $local_tmstp = filemtime($path);
  365. if ($this->_getUpdateMethod() == self::UPDATE_LOCAL) {
  366. $remote_tmstp = $this->_getLocalMTime();
  367. } else {
  368. $remote_tmstp = $this->_getRemoteMTime();
  369. }
  370. if ($remote_tmstp < $local_tmstp) {
  371. // No update needed, return
  372. touch($path);
  373. return false;
  374. }
  375. }
  376. // Get updated .ini file
  377. $browscap = $this->_getRemoteData($url);
  378. $browscap = explode("\n", $browscap);
  379. $pattern = self::REGEX_DELIMITER
  380. . '('
  381. . self::VALUES_TO_QUOTE
  382. . ')="?([^"]*)"?$'
  383. . self::REGEX_DELIMITER;
  384. // Ok, lets read the file
  385. $content = '';
  386. foreach ($browscap as $subject) {
  387. $subject = trim($subject);
  388. $content .= preg_replace($pattern, '$1="$2"', $subject) . "\n";
  389. }
  390. if (!file_put_contents($path, $content)) {
  391. throw new Browscap_Exception("Could not write .ini content to $path");
  392. }
  393. return true;
  394. }
  395. /**
  396. * Gets the remote ini file update timestamp
  397. *
  398. * @throws Browscap_Exception
  399. * @return int the remote modification timestamp
  400. */
  401. private function _getRemoteMTime()
  402. {
  403. $remote_datetime = $this->_getRemoteData($this->remoteVerUrl);
  404. $remote_tmstp = strtotime($remote_datetime);
  405. if (!$remote_tmstp) {
  406. throw new Browscap_Exception("Bad datetime format from {$this->remoteVerUrl}");
  407. }
  408. return $remote_tmstp;
  409. }
  410. /**
  411. * Gets the local ini file update timestamp
  412. *
  413. * @throws Browscap_Exception
  414. * @return int the local modification timestamp
  415. */
  416. private function _getLocalMTime()
  417. {
  418. if (!is_readable($this->localFile) || !is_file($this->localFile)) {
  419. throw new Browscap_Exception("Local file is not readable");
  420. }
  421. return filemtime($this->localFile);
  422. }
  423. /**
  424. * Converts the given array to the PHP string which represent it.
  425. * This method optimizes the PHP code and the output differs form the
  426. * var_export one as the internal PHP function does not strip whitespace or
  427. * convert strings to numbers.
  428. *
  429. * @param array $array the array to parse and convert
  430. * @return string the array parsed into a PHP string
  431. */
  432. private function _array2string($array)
  433. {
  434. $strings = array();
  435. foreach ($array as $key => $value) {
  436. if (is_int($key)) {
  437. $key = '';
  438. } else if (ctype_digit((string) $key) || strpos($key, '.0')) {
  439. $key = intval($key) . '=>' ;
  440. } else {
  441. $key = "'" . str_replace("'", "\'", $key) . "'=>" ;
  442. }
  443. if (is_array($value)) {
  444. $value = $this->_array2string($value);
  445. } else if (ctype_digit((string) $value)) {
  446. $value = intval($value);
  447. } else {
  448. $value = "'" . str_replace("'", "\'", $value) . "'";
  449. }
  450. $strings[] = $key . $value;
  451. }
  452. return 'array(' . implode(',', $strings) . ')';
  453. }
  454. /**
  455. * Checks for the various possibilities offered by the current configuration
  456. * of PHP to retrieve external HTTP data
  457. *
  458. * @return string the name of function to use to retrieve the file
  459. */
  460. private function _getUpdateMethod()
  461. {
  462. // Caches the result
  463. if ($this->updateMethod === null) {
  464. if ($this->localFile !== null) {
  465. $this->updateMethod = self::UPDATE_LOCAL;
  466. } else if (ini_get('allow_url_fopen') && function_exists('file_get_contents')) {
  467. $this->updateMethod = self::UPDATE_FOPEN;
  468. } else if (function_exists('fsockopen')) {
  469. $this->updateMethod = self::UPDATE_FSOCKOPEN;
  470. } else if (extension_loaded('curl')) {
  471. $this->updateMethod = self::UPDATE_CURL;
  472. } else {
  473. $this->updateMethod = false;
  474. }
  475. }
  476. return $this->updateMethod;
  477. }
  478. /**
  479. * Retrieve the data identified by the URL
  480. *
  481. * @param string $url the url of the data
  482. * @throws Browscap_Exception
  483. * @return string the retrieved data
  484. */
  485. private function _getRemoteData($url)
  486. {
  487. switch ($this->_getUpdateMethod()) {
  488. case self::UPDATE_LOCAL:
  489. $file = file_get_contents($url);
  490. if ($file !== false) {
  491. return $file;
  492. } else {
  493. throw new Browscap_Exception('Cannot open the local file');
  494. }
  495. case self::UPDATE_FOPEN:
  496. $file = file_get_contents($url);
  497. if ($file !== false) {
  498. return $file;
  499. } // else try with the next possibility (break omitted)
  500. case self::UPDATE_FSOCKOPEN:
  501. $remote_url = parse_url($url);
  502. $remote_handler = fsockopen($remote_url['host'], 80, $c, $e, $this->timeout);
  503. if ($remote_handler) {
  504. stream_set_timeout($remote_handler, $this->timeout);
  505. if (isset($remote_url['query'])) {
  506. $remote_url['path'] .= '?' . $remote_url['query'];
  507. }
  508. $out = sprintf(
  509. self::REQUEST_HEADERS,
  510. $remote_url['path'],
  511. $remote_url['host'],
  512. $this->_getUserAgent()
  513. );
  514. fwrite($remote_handler, $out);
  515. $response = fgets($remote_handler);
  516. if (strpos($response, '200 OK') !== false) {
  517. $file = '';
  518. while (!feof($remote_handler)) {
  519. $file .= fgets($remote_handler);
  520. }
  521. $file = str_replace("\r\n", "\n", $file);
  522. $file = explode("\n\n", $file);
  523. array_shift($file);
  524. $file = implode("\n\n", $file);
  525. fclose($remote_handler);
  526. return $file;
  527. }
  528. } // else try with the next possibility
  529. case self::UPDATE_CURL:
  530. $ch = curl_init($url);
  531. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  532. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout);
  533. curl_setopt($ch, CURLOPT_USERAGENT, $this->_getUserAgent());
  534. $file = curl_exec($ch);
  535. curl_close($ch);
  536. if ($file !== false) {
  537. return $file;
  538. } // else try with the next possibility
  539. case false:
  540. throw new Browscap_Exception('Your server can\'t connect to external resources. Please update the file manually.');
  541. }
  542. }
  543. /**
  544. * Format the useragent string to be used in the remote requests made by the
  545. * class during the update process.
  546. *
  547. * @return string the formatted user agent
  548. */
  549. private function _getUserAgent()
  550. {
  551. $ua = str_replace('%v', self::VERSION, $this->userAgent);
  552. $ua = str_replace('%m', $this->_getUpdateMethod(), $ua);
  553. return $ua;
  554. }
  555. }
  556. /**
  557. * Browscap.ini parsing class exception
  558. *
  559. * @package Browscap
  560. * @author Jonathan Stoppani <st.jonathan@gmail.com>
  561. * @copyright Copyright (c) 2006-2008 Jonathan Stoppani
  562. * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
  563. * @link http://garetjax.info/projects/browscap/
  564. */
  565. class Browscap_Exception extends Exception
  566. {}