diff --git a/htdocs/includes/magpierss/rss_cache.inc b/htdocs/includes/magpierss/rss_cache.inc
new file mode 100644
index 00000000000..3dc39cbf36b
--- /dev/null
+++ b/htdocs/includes/magpierss/rss_cache.inc
@@ -0,0 +1,184 @@
+
+ * Version: 0.51
+ * License: GPL
+ *
+ * The lastest version of MagpieRSS can be obtained from:
+ * http://magpierss.sourceforge.net
+ *
+ * For questions, help, comments, discussion, etc., please join the
+ * Magpie mailing list:
+ * http://lists.sourceforge.net/lists/listinfo/magpierss-general
+ *
+ */
+
+class RSSCache {
+ var $BASE_CACHE = './cache'; // where the cache files are stored
+ var $MAX_AGE = 3600; // when are files stale, default one hour
+ var $ERROR = ""; // accumulate error messages
+
+ function RSSCache ($base='', $age='') {
+ if ( $base ) {
+ $this->BASE_CACHE = $base;
+ }
+ if ( $age ) {
+ $this->MAX_AGE = $age;
+ }
+
+ // attempt to make the cache directory
+ if ( ! file_exists( $this->BASE_CACHE ) ) {
+ $status = @mkdir( $this->BASE_CACHE, 0755 );
+
+ // if make failed
+ if ( ! $status ) {
+ $this->error(
+ "Cache couldn't make dir '" . $this->BASE_CACHE . "'."
+ );
+ }
+ }
+ }
+
+/*=======================================================================*\
+ Function: set
+ Purpose: add an item to the cache, keyed on url
+ Input: url from wich the rss file was fetched
+ Output: true on sucess
+\*=======================================================================*/
+ function set ($url, $rss) {
+ $this->ERROR = "";
+ $cache_file = $this->file_name( $url );
+ $fp = @fopen( $cache_file, 'w' );
+
+ if ( ! $fp ) {
+ $this->error(
+ "Cache unable to open file for writing: $cache_file"
+ );
+ return 0;
+ }
+
+
+ $data = $this->serialize( $rss );
+ fwrite( $fp, $data );
+ fclose( $fp );
+
+ return $cache_file;
+ }
+
+/*=======================================================================*\
+ Function: get
+ Purpose: fetch an item from the cache
+ Input: url from wich the rss file was fetched
+ Output: cached object on HIT, false on MISS
+\*=======================================================================*/
+ function get ($url) {
+ $this->ERROR = "";
+ $cache_file = $this->file_name( $url );
+
+ if ( ! file_exists( $cache_file ) ) {
+ $this->debug(
+ "Cache doesn't contain: $url (cache file: $cache_file)"
+ );
+ return 0;
+ }
+
+ $fp = @fopen($cache_file, 'r');
+ if ( ! $fp ) {
+ $this->error(
+ "Failed to open cache file for reading: $cache_file"
+ );
+ return 0;
+ }
+
+ $data = fread( $fp, filesize($cache_file) );
+ $rss = $this->unserialize( $data );
+
+ return $rss;
+ }
+
+/*=======================================================================*\
+ Function: check_cache
+ Purpose: check a url for membership in the cache
+ and whether the object is older then MAX_AGE (ie. STALE)
+ Input: url from wich the rss file was fetched
+ Output: cached object on HIT, false on MISS
+\*=======================================================================*/
+ function check_cache ( $url ) {
+ $this->ERROR = "";
+ $filename = $this->file_name( $url );
+
+ if ( file_exists( $filename ) ) {
+ // find how long ago the file was added to the cache
+ // and whether that is longer then MAX_AGE
+ $mtime = filemtime( $filename );
+ $age = time() - $mtime;
+ if ( $this->MAX_AGE > $age ) {
+ // object exists and is current
+ return 'HIT';
+ }
+ else {
+ // object exists but is old
+ return 'STALE';
+ }
+ }
+ else {
+ // object does not exist
+ return 'MISS';
+ }
+ }
+
+/*=======================================================================*\
+ Function: serialize
+\*=======================================================================*/
+ function serialize ( $rss ) {
+ return serialize( $rss );
+ }
+
+/*=======================================================================*\
+ Function: unserialize
+\*=======================================================================*/
+ function unserialize ( $data ) {
+ return unserialize( $data );
+ }
+
+/*=======================================================================*\
+ Function: file_name
+ Purpose: map url to location in cache
+ Input: url from wich the rss file was fetched
+ Output: a file name
+\*=======================================================================*/
+ function file_name ($url) {
+ $filename = md5( $url );
+ return join( DIRECTORY_SEPARATOR, array( $this->BASE_CACHE, $filename ) );
+ }
+
+/*=======================================================================*\
+ Function: error
+ Purpose: register error
+\*=======================================================================*/
+ function error ($errormsg, $lvl=E_USER_WARNING) {
+ // append PHP's error message if track_errors enabled
+ if ( isset($php_errormsg) ) {
+ $errormsg .= " ($php_errormsg)";
+ }
+ $this->ERROR = $errormsg;
+ if ( MAGPIE_DEBUG ) {
+ trigger_error( $errormsg, $lvl);
+ }
+ else {
+ error_log( $errormsg, 0);
+ }
+ }
+
+ function debug ($debugmsg, $lvl=E_USER_NOTICE) {
+ if ( MAGPIE_DEBUG ) {
+ $this->error("MagpieRSS [debug] $debugmsg", $lvl);
+ }
+ }
+
+}
+
+?>
diff --git a/htdocs/includes/magpierss/rss_fetch.inc b/htdocs/includes/magpierss/rss_fetch.inc
new file mode 100644
index 00000000000..6afdbbeb680
--- /dev/null
+++ b/htdocs/includes/magpierss/rss_fetch.inc
@@ -0,0 +1,439 @@
+
+ * Version: 0.51
+ * License: GPL
+ *
+ * The lastest version of MagpieRSS can be obtained from:
+ * http://magpierss.sourceforge.net
+ *
+ * For questions, help, comments, discussion, etc., please join the
+ * Magpie mailing list:
+ * magpierss-general@lists.sourceforge.net
+ *
+ */
+
+// Setup MAGPIE_DIR for use on hosts that don't include
+// the current path in include_path.
+// with thanks to rajiv and smarty
+if (!defined('DIR_SEP')) {
+ define('DIR_SEP', DIRECTORY_SEPARATOR);
+}
+
+if (!defined('MAGPIE_DIR')) {
+ define('MAGPIE_DIR', dirname(__FILE__) . DIR_SEP);
+}
+
+require_once( MAGPIE_DIR . 'rss_parse.inc' );
+require_once( MAGPIE_DIR . 'rss_cache.inc' );
+
+// for including 3rd party libraries
+define('MAGPIE_EXTLIB', MAGPIE_DIR . 'extlib' . DIR_SEP);
+require_once( MAGPIE_EXTLIB . 'Snoopy.class.inc');
+
+
+/*
+ * CONSTANTS - redefine these in your script to change the
+ * behaviour of fetch_rss() currently, most options effect the cache
+ *
+ * MAGPIE_CACHE_ON - Should Magpie cache parsed RSS objects?
+ * For me a built in cache was essential to creating a "PHP-like"
+ * feel to Magpie, see rss_cache.inc for rationale
+ *
+ *
+ * MAGPIE_CACHE_DIR - Where should Magpie cache parsed RSS objects?
+ * This should be a location that the webserver can write to. If this
+ * directory does not already exist Mapie will try to be smart and create
+ * it. This will often fail for permissions reasons.
+ *
+ *
+ * MAGPIE_CACHE_AGE - How long to store cached RSS objects? In seconds.
+ *
+ *
+ * MAGPIE_CACHE_FRESH_ONLY - If remote fetch fails, throw error
+ * instead of returning stale object?
+ *
+ * MAGPIE_DEBUG - Display debugging notices?
+ *
+*/
+
+
+/*=======================================================================*\
+ Function: fetch_rss:
+ Purpose: return RSS object for the give url
+ maintain the cache
+ Input: url of RSS file
+ Output: parsed RSS object (see rss_parse.inc)
+
+ NOTES ON CACHEING:
+ If caching is on (MAGPIE_CACHE_ON) fetch_rss will first check the cache.
+
+ NOTES ON RETRIEVING REMOTE FILES:
+ If conditional gets are on (MAGPIE_CONDITIONAL_GET_ON) fetch_rss will
+ return a cached object, and touch the cache object upon recieving a
+ 304.
+
+ NOTES ON FAILED REQUESTS:
+ If there is an HTTP error while fetching an RSS object, the cached
+ version will be return, if it exists (and if MAGPIE_CACHE_FRESH_ONLY is off)
+\*=======================================================================*/
+
+define('MAGPIE_VERSION', '0.51');
+
+$MAGPIE_ERROR = "";
+
+function fetch_rss ($url) {
+ // initialize constants
+ init();
+
+ if ( !isset($url) ) {
+ error("fetch_rss called without a url");
+ return false;
+ }
+
+ // if cache is disabled
+ if ( !MAGPIE_CACHE_ON ) {
+ // fetch file, and parse it
+ $resp = _fetch_remote_file( $url );
+ if ( is_success( $resp->status ) ) {
+ return _response_to_rss( $resp );
+ }
+ else {
+ error("Failed to fetch $url and cache is off");
+ return false;
+ }
+ }
+ // else cache is ON
+ else {
+ // Flow
+ // 1. check cache
+ // 2. if there is a hit, make sure its fresh
+ // 3. if cached obj fails freshness check, fetch remote
+ // 4. if remote fails, return stale object, or error
+
+ $cache = new RSSCache( MAGPIE_CACHE_DIR, MAGPIE_CACHE_AGE );
+
+ if (MAGPIE_DEBUG and $cache->ERROR) {
+ debug($cache->ERROR, E_USER_WARNING);
+ }
+
+
+ $cache_status = 0; // response of check_cache
+ $request_headers = array(); // HTTP headers to send with fetch
+ $rss = 0; // parsed RSS object
+ $errormsg = 0; // errors, if any
+
+ if (!$cache->ERROR) {
+ // return cache HIT, MISS, or STALE
+ $cache_status = $cache->check_cache( $url );
+ }
+
+ // if object cached, and cache is fresh, return cached obj
+ if ( $cache_status == 'HIT' ) {
+ $rss = $cache->get( $url );
+ if ( isset($rss) and $rss ) {
+ $rss->from_cache = 1;
+ if ( MAGPIE_DEBUG > 1) {
+ debug("MagpieRSS: Cache HIT", E_USER_NOTICE);
+ }
+ return $rss;
+ }
+ }
+
+ // else attempt a conditional get
+
+ // setup headers
+ if ( $cache_status == 'STALE' ) {
+ $rss = $cache->get( $url );
+ if ( $rss->etag and $rss->last_modified ) {
+ $request_headers['If-None-Match'] = $rss->etag;
+ $request_headers['If-Last-Modified'] = $rss->last_modified;
+ }
+ }
+
+ $resp = _fetch_remote_file( $url, $request_headers );
+
+ if (isset($resp) and $resp) {
+ if ($resp->status == '304' ) {
+ // we have the most current copy
+ if ( MAGPIE_DEBUG > 1) {
+ debug("Got 304 for $url");
+ }
+ // reset cache on 304 (at minutillo insistent prodding)
+ $cache->set($url, $rss);
+ return $rss;
+ }
+ elseif ( is_success( $resp->status ) ) {
+ $rss = _response_to_rss( $resp );
+ if ( $rss ) {
+ if (MAGPIE_DEBUG > 1) {
+ debug("Fetch successful");
+ }
+ // add object to cache
+ $cache->set( $url, $rss );
+ return $rss;
+ }
+ }
+ else {
+ $errormsg = "Failed to fetch $url. ";
+ if ( $resp->error ) {
+ # compensate for Snoopy's annoying habbit to tacking
+ # on '\n'
+ $http_error = substr($resp->error, 0, -2);
+ $errormsg .= "(HTTP Error: $http_error)";
+ }
+ else {
+ $errormsg .= "(HTTP Response: " . $resp->response_code .')';
+ }
+ }
+ }
+ else {
+ $errormsg = "Unable to retrieve RSS file for unknown reasons.";
+ }
+
+ // else fetch failed
+
+ // attempt to return cached object
+ if ($rss) {
+ if ( MAGPIE_DEBUG ) {
+ debug("Returning STALE object for $url");
+ }
+ return $rss;
+ }
+
+ // else we totally failed
+ error( $errormsg );
+
+ return false;
+
+ } // end if ( !MAGPIE_CACHE_ON ) {
+} // end fetch_rss()
+
+/*=======================================================================*\
+ Function: error
+ Purpose: set MAGPIE_ERROR, and trigger error
+\*=======================================================================*/
+
+function error ($errormsg, $lvl=E_USER_WARNING) {
+ global $MAGPIE_ERROR;
+
+ // append PHP's error message if track_errors enabled
+ if ( isset($php_errormsg) ) {
+ $errormsg .= " ($php_errormsg)";
+ }
+ if ( $errormsg ) {
+ $errormsg = "MagpieRSS: $errormsg";
+ $MAGPIE_ERROR = $errormsg;
+ trigger_error( $errormsg, $lvl);
+ }
+}
+
+function debug ($debugmsg, $lvl=E_USER_NOTICE) {
+ trigger_error("MagpieRSS [debug] $debugmsg", $lvl);
+}
+
+/*=======================================================================*\
+ Function: magpie_error
+ Purpose: accessor for the magpie error variable
+\*=======================================================================*/
+function magpie_error ($errormsg="") {
+ global $MAGPIE_ERROR;
+
+ if ( isset($errormsg) and $errormsg ) {
+ $MAGPIE_ERROR = $errormsg;
+ }
+
+ return $MAGPIE_ERROR;
+}
+
+/*=======================================================================*\
+ Function: _fetch_remote_file
+ Purpose: retrieve an arbitrary remote file
+ Input: url of the remote file
+ headers to send along with the request (optional)
+ Output: an HTTP response object (see Snoopy.class.inc)
+\*=======================================================================*/
+function _fetch_remote_file ($url, $headers = "" ) {
+ // Snoopy is an HTTP client in PHP
+ $client = new Snoopy();
+ $client->agent = MAGPIE_USER_AGENT;
+ $client->read_timeout = MAGPIE_FETCH_TIME_OUT;
+ $client->use_gzip = MAGPIE_USE_GZIP;
+ if (is_array($headers) ) {
+ $client->rawheaders = $headers;
+ }
+
+ @$client->fetch($url);
+ return $client;
+
+}
+
+/*=======================================================================*\
+ Function: _response_to_rss
+ Purpose: parse an HTTP response object into an RSS object
+ Input: an HTTP response object (see Snoopy)
+ Output: parsed RSS object (see rss_parse)
+\*=======================================================================*/
+function _response_to_rss ($resp) {
+ $rss = new MagpieRSS( $resp->results );
+
+ // if RSS parsed successfully
+ if ( $rss and !$rss->ERROR) {
+
+ // find Etag, and Last-Modified
+ foreach($resp->headers as $h) {
+ // 2003-03-02 - Nicola Asuni (www.tecnick.com) - fixed bug "Undefined offset: 1"
+ if (strpos($h, ": ")) {
+ list($field, $val) = explode(": ", $h, 2);
+ }
+ else {
+ $field = $h;
+ $val = "";
+ }
+
+ if ( $field == 'ETag' ) {
+ $rss->etag = $val;
+ }
+
+ if ( $field == 'Last-Modified' ) {
+ $rss->last_modified = $val;
+ }
+ }
+
+ return $rss;
+ } // else construct error message
+ else {
+ $errormsg = "Failed to parse RSS file.";
+
+ if ($rss) {
+ $errormsg .= " (" . $rss->ERROR . ")";
+ }
+ error($errormsg);
+
+ return false;
+ } // end if ($rss and !$rss->error)
+}
+
+/*=======================================================================*\
+ Function: init
+ Purpose: setup constants with default values
+ check for user overrides
+\*=======================================================================*/
+function init () {
+ if ( defined('MAGPIE_INITALIZED') ) {
+ return;
+ }
+ else {
+ define('MAGPIE_INITALIZED', 1);
+ }
+
+ if ( !defined('MAGPIE_CACHE_ON') ) {
+ define('MAGPIE_CACHE_ON', 1);
+ }
+
+ if ( !defined('MAGPIE_CACHE_DIR') ) {
+ define('MAGPIE_CACHE_DIR', './cache');
+ }
+
+ if ( !defined('MAGPIE_CACHE_AGE') ) {
+ define('MAGPIE_CACHE_AGE', 60*60); // one hour
+ }
+
+ if ( !defined('MAGPIE_CACHE_FRESH_ONLY') ) {
+ define('MAGPIE_CACHE_FRESH_ONLY', 0);
+ }
+
+ if ( !defined('MAGPIE_DEBUG') ) {
+ define('MAGPIE_DEBUG', 0);
+ }
+
+ if ( !defined('MAGPIE_USER_AGENT') ) {
+ $ua = 'MagpieRSS/'. MAGPIE_VERSION . ' (+http://magpierss.sf.net';
+
+ if ( MAGPIE_CACHE_ON ) {
+ $ua = $ua . ')';
+ }
+ else {
+ $ua = $ua . '; No cache)';
+ }
+
+ define('MAGPIE_USER_AGENT', $ua);
+ }
+
+ if ( !defined('MAGPIE_FETCH_TIME_OUT') ) {
+ define('MAGPIE_FETCH_TIME_OUT', 5); // 5 second timeout
+ }
+
+ // use gzip encoding to fetch rss files if supported?
+ if ( !defined('MAGPIE_USE_GZIP') ) {
+ define('MAGPIE_USE_GZIP', true);
+ }
+}
+
+// NOTE: the following code should really be in Snoopy, or at least
+// somewhere other then rss_fetch!
+
+/*=======================================================================*\
+ HTTP STATUS CODE PREDICATES
+ These functions attempt to classify an HTTP status code
+ based on RFC 2616 and RFC 2518.
+
+ All of them take an HTTP status code as input, and return true or false
+
+ All this code is adapted from LWP's HTTP::Status.
+\*=======================================================================*/
+
+
+/*=======================================================================*\
+ Function: is_info
+ Purpose: return true if Informational status code
+\*=======================================================================*/
+function is_info ($sc) {
+ return $sc >= 100 && $sc < 200;
+}
+
+/*=======================================================================*\
+ Function: is_success
+ Purpose: return true if Successful status code
+\*=======================================================================*/
+function is_success ($sc) {
+ return $sc >= 200 && $sc < 300;
+}
+
+/*=======================================================================*\
+ Function: is_redirect
+ Purpose: return true if Redirection status code
+\*=======================================================================*/
+function is_redirect ($sc) {
+ return $sc >= 300 && $sc < 400;
+}
+
+/*=======================================================================*\
+ Function: is_error
+ Purpose: return true if Error status code
+\*=======================================================================*/
+function is_error ($sc) {
+ return $sc >= 400 && $sc < 600;
+}
+
+/*=======================================================================*\
+ Function: is_client_error
+ Purpose: return true if Error status code, and its a client error
+\*=======================================================================*/
+function is_client_error ($sc) {
+ return $sc >= 400 && $sc < 500;
+}
+
+/*=======================================================================*\
+ Function: is_client_error
+ Purpose: return true if Error status code, and its a server error
+\*=======================================================================*/
+function is_server_error ($sc) {
+ return $sc >= 500 && $sc < 600;
+}
+
+?>
diff --git a/htdocs/includes/magpierss/rss_parse.inc b/htdocs/includes/magpierss/rss_parse.inc
new file mode 100644
index 00000000000..5566ecc53c3
--- /dev/null
+++ b/htdocs/includes/magpierss/rss_parse.inc
@@ -0,0 +1,338 @@
+
+ * Version: 0.51
+ * License: GPL
+ *
+ * The lastest version of MagpieRSS can be obtained from:
+ * http://magpierss.sourceforge.net
+ *
+ * For questions, help, comments, discussion, etc., please join the
+ * Magpie mailing list:
+ * magpierss-general@lists.sourceforge.net
+ *
+ */
+
+
+/*
+ * NOTES ON RSS PARSING PHILOSOPHY (moderately important):
+ * MagpieRSS parse all versions of RSS with a few limitation (mod_content, and
+ * mod_taxonomy support is shaky) into a simple object, with 2 fields,
+ * the hash 'channel', and the array 'items'.
+ *
+ * MagpieRSS is a forgiving and inclusive parser. It currently makes no
+ * attempt to enforce the validity on an RSS feed. It will include any
+ * properly formatted tags it finds, allowing to you to mix RSS 0.93, with RSS
+ * 1.0, with tags or your own imagining. This sort of witches brew is a bad
+ * bad idea! But Magpie is less pendantic then I am.
+ *
+ * RSS validators are readily available on the web at:
+ * http://feeds.archive.org/validator/
+ * http://www.ldodds.com/rss_validator/1.0/validator.html
+ *
+ */
+
+/*
+ * EXAMPLE PARSE RESULTS:
+ *
+ * Magpie tries to parse RSS into ease to use PHP datastructures.
+ *
+ * For example, Magpie on encountering RSS 1.0 item entry:
+ *
+ *