rss_fetch.inc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. <?php
  2. /*
  3. * Project: MagpieRSS: a simple RSS integration tool
  4. * File: rss_fetch.inc, a simple functional interface
  5. to fetching and parsing RSS files, via the
  6. function fetch_rss()
  7. * Author: Kellan Elliott-McCrea <kellan@protest.net>
  8. * License: GPL
  9. *
  10. * The lastest version of MagpieRSS can be obtained from:
  11. * http://magpierss.sourceforge.net
  12. *
  13. * For questions, help, comments, discussion, etc., please join the
  14. * Magpie mailing list:
  15. * magpierss-general@lists.sourceforge.net
  16. *
  17. */
  18. // Setup MAGPIE_DIR for use on hosts that don't include
  19. // the current path in include_path.
  20. // with thanks to rajiv and smarty
  21. if (!defined('DIR_SEP')) {
  22. define('DIR_SEP', DIRECTORY_SEPARATOR);
  23. }
  24. if (!defined('MAGPIE_DIR')) {
  25. define('MAGPIE_DIR', dirname(__FILE__) . DIR_SEP);
  26. }
  27. require_once( MAGPIE_DIR . 'rss_parse.inc' );
  28. require_once( MAGPIE_DIR . 'rss_cache.inc' );
  29. // for including 3rd party libraries
  30. define('MAGPIE_EXTLIB', MAGPIE_DIR . 'extlib' . DIR_SEP);
  31. require_once( MAGPIE_EXTLIB . 'Snoopy.class.inc');
  32. define('MAGPIE_CACHE_DIR', api_get_path(SYS_ARCHIVE_PATH));
  33. /*
  34. * CONSTANTS - redefine these in your script to change the
  35. * behaviour of fetch_rss() currently, most options effect the cache
  36. *
  37. * MAGPIE_CACHE_ON - Should Magpie cache parsed RSS objects?
  38. * For me a built in cache was essential to creating a "PHP-like"
  39. * feel to Magpie, see rss_cache.inc for rationale
  40. *
  41. *
  42. * MAGPIE_CACHE_DIR - Where should Magpie cache parsed RSS objects?
  43. * This should be a location that the webserver can write to. If this
  44. * directory does not already exist Mapie will try to be smart and create
  45. * it. This will often fail for permissions reasons.
  46. *
  47. *
  48. * MAGPIE_CACHE_AGE - How long to store cached RSS objects? In seconds.
  49. *
  50. *
  51. * MAGPIE_CACHE_FRESH_ONLY - If remote fetch fails, throw error
  52. * instead of returning stale object?
  53. *
  54. * MAGPIE_DEBUG - Display debugging notices?
  55. *
  56. */
  57. /*=======================================================================*\
  58. Function: fetch_rss:
  59. Purpose: return RSS object for the give url
  60. maintain the cache
  61. Input: url of RSS file
  62. Output: parsed RSS object (see rss_parse.inc)
  63. NOTES ON CACHEING:
  64. If caching is on (MAGPIE_CACHE_ON) fetch_rss will first check the cache.
  65. NOTES ON RETRIEVING REMOTE FILES:
  66. If conditional gets are on (MAGPIE_CONDITIONAL_GET_ON) fetch_rss will
  67. return a cached object, and touch the cache object upon recieving a
  68. 304.
  69. NOTES ON FAILED REQUESTS:
  70. If there is an HTTP error while fetching an RSS object, the cached
  71. version will be return, if it exists (and if MAGPIE_CACHE_FRESH_ONLY is off)
  72. \*=======================================================================*/
  73. define('MAGPIE_VERSION', '0.72');
  74. $MAGPIE_ERROR = "";
  75. function fetch_rss ($url) {
  76. // initialize constants
  77. init();
  78. if ( !isset($url) ) {
  79. error("fetch_rss called without a url");
  80. return false;
  81. }
  82. // if cache is disabled
  83. if ( !MAGPIE_CACHE_ON ) {
  84. // fetch file, and parse it
  85. $resp = _fetch_remote_file( $url );
  86. if ( is_success( $resp->status ) ) {
  87. return _response_to_rss( $resp );
  88. }
  89. else {
  90. error("Failed to fetch $url and cache is off");
  91. return false;
  92. }
  93. }
  94. // else cache is ON
  95. else {
  96. // Flow
  97. // 1. check cache
  98. // 2. if there is a hit, make sure its fresh
  99. // 3. if cached obj fails freshness check, fetch remote
  100. // 4. if remote fails, return stale object, or error
  101. $cache = new RSSCache( MAGPIE_CACHE_DIR, MAGPIE_CACHE_AGE );
  102. if (MAGPIE_DEBUG and $cache->ERROR) {
  103. debug($cache->ERROR, E_USER_WARNING);
  104. }
  105. $cache_status = 0; // response of check_cache
  106. $request_headers = array(); // HTTP headers to send with fetch
  107. $rss = 0; // parsed RSS object
  108. $errormsg = 0; // errors, if any
  109. // store parsed XML by desired output encoding
  110. // as character munging happens at parse time
  111. $cache_key = $url . MAGPIE_OUTPUT_ENCODING;
  112. if (!$cache->ERROR) {
  113. // return cache HIT, MISS, or STALE
  114. $cache_status = $cache->check_cache( $cache_key);
  115. }
  116. // if object cached, and cache is fresh, return cached obj
  117. if ( $cache_status == 'HIT' ) {
  118. $rss = $cache->get( $cache_key );
  119. if ( isset($rss) and $rss ) {
  120. // should be cache age
  121. $rss->from_cache = 1;
  122. if ( MAGPIE_DEBUG > 1) {
  123. debug("MagpieRSS: Cache HIT", E_USER_NOTICE);
  124. }
  125. return $rss;
  126. }
  127. }
  128. // else attempt a conditional get
  129. // setup headers
  130. if ( $cache_status == 'STALE' ) {
  131. $rss = $cache->get( $cache_key );
  132. if ( $rss and $rss->etag and $rss->last_modified ) {
  133. $request_headers['If-None-Match'] = $rss->etag;
  134. $request_headers['If-Last-Modified'] = $rss->last_modified;
  135. }
  136. }
  137. $resp = _fetch_remote_file( $url, $request_headers );
  138. if (isset($resp) and $resp) {
  139. if ($resp->status == '304' ) {
  140. // we have the most current copy
  141. if ( MAGPIE_DEBUG > 1) {
  142. debug("Got 304 for $url");
  143. }
  144. // reset cache on 304 (at minutillo insistent prodding)
  145. $cache->set($cache_key, $rss);
  146. return $rss;
  147. }
  148. elseif ( is_success( $resp->status ) ) {
  149. $rss = _response_to_rss( $resp );
  150. if ( $rss ) {
  151. if (MAGPIE_DEBUG > 1) {
  152. debug("Fetch successful");
  153. }
  154. // add object to cache
  155. $cache->set( $cache_key, $rss );
  156. return $rss;
  157. }
  158. }
  159. else {
  160. $errormsg = "Failed to fetch $url ";
  161. if ( $resp->status == '-100' ) {
  162. $errormsg .= "(Request timed out after " . MAGPIE_FETCH_TIME_OUT . " seconds)";
  163. }
  164. elseif ( $resp->error ) {
  165. # compensate for Snoopy's annoying habbit to tacking
  166. # on '\n'
  167. $http_error = substr($resp->error, 0, -2);
  168. $errormsg .= "(HTTP Error: $http_error)";
  169. }
  170. else {
  171. $errormsg .= "(HTTP Response: " . $resp->response_code .')';
  172. }
  173. }
  174. }
  175. else {
  176. $errormsg = "Unable to retrieve RSS file for unknown reasons.";
  177. }
  178. // else fetch failed
  179. // attempt to return cached object
  180. if ($rss) {
  181. if ( MAGPIE_DEBUG ) {
  182. debug("Returning STALE object for $url");
  183. }
  184. return $rss;
  185. }
  186. // else we totally failed
  187. error( $errormsg );
  188. return false;
  189. } // end if ( !MAGPIE_CACHE_ON ) {
  190. } // end fetch_rss()
  191. /*=======================================================================*\
  192. Function: error
  193. Purpose: set MAGPIE_ERROR, and trigger error
  194. \*=======================================================================*/
  195. function error ($errormsg, $lvl=E_USER_WARNING) {
  196. global $MAGPIE_ERROR;
  197. // append PHP's error message if track_errors enabled
  198. if ( isset($php_errormsg) ) {
  199. $errormsg .= " ($php_errormsg)";
  200. }
  201. if ( $errormsg ) {
  202. $errormsg = "MagpieRSS: $errormsg";
  203. $MAGPIE_ERROR = $errormsg;
  204. trigger_error( $errormsg, $lvl);
  205. }
  206. }
  207. function debug ($debugmsg, $lvl=E_USER_NOTICE) {
  208. trigger_error("MagpieRSS [debug] $debugmsg", $lvl);
  209. }
  210. /*=======================================================================*\
  211. Function: magpie_error
  212. Purpose: accessor for the magpie error variable
  213. \*=======================================================================*/
  214. function magpie_error ($errormsg="") {
  215. global $MAGPIE_ERROR;
  216. if ( isset($errormsg) and $errormsg ) {
  217. $MAGPIE_ERROR = $errormsg;
  218. }
  219. return $MAGPIE_ERROR;
  220. }
  221. /*=======================================================================*\
  222. Function: _fetch_remote_file
  223. Purpose: retrieve an arbitrary remote file
  224. Input: url of the remote file
  225. headers to send along with the request (optional)
  226. Output: an HTTP response object (see Snoopy.class.inc)
  227. \*=======================================================================*/
  228. function _fetch_remote_file ($url, $headers = "" ) {
  229. // Snoopy is an HTTP client in PHP
  230. $client = new Snoopy();
  231. $client->agent = MAGPIE_USER_AGENT;
  232. $client->read_timeout = MAGPIE_FETCH_TIME_OUT;
  233. $client->use_gzip = MAGPIE_USE_GZIP;
  234. if (is_array($headers) ) {
  235. $client->rawheaders = $headers;
  236. }
  237. @$client->fetch($url);
  238. return $client;
  239. }
  240. /*=======================================================================*\
  241. Function: _response_to_rss
  242. Purpose: parse an HTTP response object into an RSS object
  243. Input: an HTTP response object (see Snoopy)
  244. Output: parsed RSS object (see rss_parse)
  245. \*=======================================================================*/
  246. function _response_to_rss ($resp) {
  247. $rss = new MagpieRSS( $resp->results, MAGPIE_OUTPUT_ENCODING, MAGPIE_INPUT_ENCODING, MAGPIE_DETECT_ENCODING );
  248. // if RSS parsed successfully
  249. if ( $rss and !$rss->ERROR) {
  250. // find Etag, and Last-Modified
  251. foreach($resp->headers as $h) {
  252. // 2003-03-02 - Nicola Asuni (www.tecnick.com) - fixed bug "Undefined offset: 1"
  253. if (strpos($h, ": ")) {
  254. list($field, $val) = explode(": ", $h, 2);
  255. }
  256. else {
  257. $field = $h;
  258. $val = "";
  259. }
  260. if ( $field == 'ETag' ) {
  261. $rss->etag = $val;
  262. }
  263. if ( $field == 'Last-Modified' ) {
  264. $rss->last_modified = $val;
  265. }
  266. }
  267. return $rss;
  268. } // else construct error message
  269. else {
  270. $errormsg = "Failed to parse RSS file.";
  271. if ($rss) {
  272. $errormsg .= " (" . $rss->ERROR . ")";
  273. }
  274. error($errormsg,E_USER_NOTICE);
  275. return false;
  276. } // end if ($rss and !$rss->error)
  277. }
  278. /*=======================================================================*\
  279. Function: init
  280. Purpose: setup constants with default values
  281. check for user overrides
  282. \*=======================================================================*/
  283. function init () {
  284. if ( defined('MAGPIE_INITALIZED') ) {
  285. return;
  286. }
  287. else {
  288. define('MAGPIE_INITALIZED', true);
  289. }
  290. if ( !defined('MAGPIE_CACHE_ON') ) {
  291. define('MAGPIE_CACHE_ON', true);
  292. }
  293. if ( !defined('MAGPIE_CACHE_DIR') ) {
  294. define('MAGPIE_CACHE_DIR', './cache');
  295. }
  296. if ( !defined('MAGPIE_CACHE_AGE') ) {
  297. define('MAGPIE_CACHE_AGE', 60*60); // one hour
  298. }
  299. if ( !defined('MAGPIE_CACHE_FRESH_ONLY') ) {
  300. define('MAGPIE_CACHE_FRESH_ONLY', false);
  301. }
  302. if ( !defined('MAGPIE_OUTPUT_ENCODING') ) {
  303. define('MAGPIE_OUTPUT_ENCODING', 'ISO-8859-1');
  304. }
  305. if ( !defined('MAGPIE_INPUT_ENCODING') ) {
  306. define('MAGPIE_INPUT_ENCODING', null);
  307. }
  308. if ( !defined('MAGPIE_DETECT_ENCODING') ) {
  309. define('MAGPIE_DETECT_ENCODING', true);
  310. }
  311. if ( !defined('MAGPIE_DEBUG') ) {
  312. define('MAGPIE_DEBUG', 0);
  313. }
  314. if ( !defined('MAGPIE_USER_AGENT') ) {
  315. $ua = 'MagpieRSS/'. MAGPIE_VERSION . ' (+http://magpierss.sf.net';
  316. if ( MAGPIE_CACHE_ON ) {
  317. $ua = $ua . ')';
  318. }
  319. else {
  320. $ua = $ua . '; No cache)';
  321. }
  322. define('MAGPIE_USER_AGENT', $ua);
  323. }
  324. if ( !defined('MAGPIE_FETCH_TIME_OUT') ) {
  325. define('MAGPIE_FETCH_TIME_OUT', 5); // 5 second timeout
  326. }
  327. // use gzip encoding to fetch rss files if supported?
  328. if ( !defined('MAGPIE_USE_GZIP') ) {
  329. define('MAGPIE_USE_GZIP', true);
  330. }
  331. }
  332. // NOTE: the following code should really be in Snoopy, or at least
  333. // somewhere other then rss_fetch!
  334. /*=======================================================================*\
  335. HTTP STATUS CODE PREDICATES
  336. These functions attempt to classify an HTTP status code
  337. based on RFC 2616 and RFC 2518.
  338. All of them take an HTTP status code as input, and return true or false
  339. All this code is adapted from LWP's HTTP::Status.
  340. \*=======================================================================*/
  341. /*=======================================================================*\
  342. Function: is_info
  343. Purpose: return true if Informational status code
  344. \*=======================================================================*/
  345. function is_info ($sc) {
  346. return $sc >= 100 && $sc < 200;
  347. }
  348. /*=======================================================================*\
  349. Function: is_success
  350. Purpose: return true if Successful status code
  351. \*=======================================================================*/
  352. function is_success ($sc) {
  353. return $sc >= 200 && $sc < 300;
  354. }
  355. /*=======================================================================*\
  356. Function: is_redirect
  357. Purpose: return true if Redirection status code
  358. \*=======================================================================*/
  359. function is_redirect ($sc) {
  360. return $sc >= 300 && $sc < 400;
  361. }
  362. /*=======================================================================*\
  363. Function: is_error
  364. Purpose: return true if Error status code
  365. \*=======================================================================*/
  366. function is_error ($sc) {
  367. return $sc >= 400 && $sc < 600;
  368. }
  369. /*=======================================================================*\
  370. Function: is_client_error
  371. Purpose: return true if Error status code, and its a client error
  372. \*=======================================================================*/
  373. function is_client_error ($sc) {
  374. return $sc >= 400 && $sc < 500;
  375. }
  376. /*=======================================================================*\
  377. Function: is_client_error
  378. Purpose: return true if Error status code, and its a server error
  379. \*=======================================================================*/
  380. function is_server_error ($sc) {
  381. return $sc >= 500 && $sc < 600;
  382. }