http://example.com/
* and return an object that can produce rendered output.
*/
function __construct( $url, $args ) {
$this->url = $url;
# Get max number of headlines from argument-array
if ( isset( $args['max'] ) ) {
$this->maxheads = $args['max'];
}
# Get reverse flag from argument array
if ( isset( $args['reverse'] ) ) {
$this->reversed = true;
}
# Get date format from argument array
# FIXME: not used yet
if ( isset( $args['date'] ) ) {
$this->date = $args['date'];
}
# Get highlight terms from argument array
if ( isset( $args['highlight'] ) ) {
# mapping to lowercase here so the regex can be case insensitive below.
$this->highlight = self::explodeOnSpaces( $args['highlight'] );
}
# Get filter terms from argument array
if ( isset( $args['filter'] ) ) {
$this->filter = self::explodeOnSpaces( $args['filter'] );
}
if ( isset( $args['filterout'] ) ) {
$this->filterOut = self::explodeOnSpaces( $args['filterout'] );
}
// 'template' is the pagename of a user's itemTemplate including
// a further pagename for the feedTemplate
// In that way everything is handled via these two pages
// and no default pages or templates are used.
// 'templatename' is an optional pagename of a user's feedTemplate
// In that way it substitutes $1 (default: RSSPost) in MediaWiki:Rss-item
if ( isset( $args['template'] ) ) {
$itemTemplateTitleObject = Title::newFromText( $args['template'], NS_TEMPLATE );
$itemTemplateArticleObject = new Article( $itemTemplateTitleObject, 0 );
$this->itemTemplate = $itemTemplateArticleObject->fetchContent();
} else {
if ( isset( $args['templatename'] ) ) {
$feedTemplatePagename = $args['templatename'];
} else {
// compatibility patch for rss extension
$feedTemplatePagename = 'RSSPost';
$feedTemplateTitleObject = Title::newFromText( $feedTemplatePagename, NS_TEMPLATE );
if ( !$feedTemplateTitleObject->exists() ) {
$feedTemplatePagename = Title::makeTitleSafe( NS_MEDIAWIKI, 'Rss-feed' );
}
}
// MediaWiki:Rss-item = {{ feedTemplatePagename | title = {{{title}}} | ... }}
// if the attribute parameter templatename= is not present
// then it defaults to
// {{ Template:RSSPost | title = {{{title}}} | ... }} - if Template:RSSPost exists from pre-1.9 versions
// {{ MediaWiki:Rss-feed | title = {{{title}}} | ... }} - otherwise
$this->itemTemplate = wfMsgNoTrans( 'rss-item', $feedTemplatePagename );
}
}
/**
* Return RSS object for the given URL, maintaining caching.
*
* NOTES ON RETRIEVING REMOTE FILES:
* No attempt will be made to fetch remote files if there is something in cache.
*
* NOTES ON FAILED REQUESTS:
* If there is an HTTP error while fetching an RSS object, the cached version
* will be returned, if it exists.
*
* @return Status object
*/
function fetch() {
if ( !isset( $this->url ) ) {
return Status::newFatal( 'rss-fetch-nourl' );
}
// 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
$key = wfMemcKey( 'rss', $this->url );
$cachedFeed = $this->loadFromCache( $key );
if ( $cachedFeed !== false ) {
wfDebugLog( 'RSS', 'Outputting cached feed for ' . $this->url );
return Status::newGood();
}
wfDebugLog( 'RSS', 'Cache Failed, fetching ' . $this->url . ' from remote.' );
$status = $this->fetchRemote( $key );
return $status;
}
/**
* Retrieve the URL from the cache
* @param $key String: lookup key to associate with this item
* @return boolean
*/
protected function loadFromCache( $key ) {
global $wgMemc, $wgRSSCacheCompare;
$data = $wgMemc->get( $key );
if ( !is_array( $data ) ) {
return false;
}
list( $etag, $lastModified, $rss ) =
$data;
if ( !isset( $rss->items ) ) {
return false;
}
wfDebugLog( 'RSS', "Got '$key' from cache" );
# Now that we've verified that we got useful data, keep it around.
$this->rss = $rss;
$this->etag = $etag;
$this->lastModified = $lastModified;
// We only care if $wgRSSCacheCompare is > 0
if ( $wgRSSCacheCompare && time() - $wgRSSCacheCompare > $lastModified ) {
wfDebugLog( 'RSS', 'Content is old enough that we need to check cached content' );
return false;
}
return true;
}
/**
* Store these objects (i.e. etag, lastModified, and RSS) in the cache.
* @param $key String: lookup key to associate with this item
* @return boolean
*/
protected function storeInCache( $key ) {
global $wgMemc, $wgRSSCacheAge;
if ( !isset( $this->rss ) ) {
return false;
}
$r = $wgMemc->set( $key,
array( $this->etag, $this->lastModified, $this->rss ),
$wgRSSCacheAge );
wfDebugLog( 'RSS', "Stored '$key' as in cache? $r");
return true;
}
/**
* Retrieve a feed.
* @param $key String:
* @param $headers Array: headers to send along with the request
* @return Status object
*/
protected function fetchRemote( $key, array $headers = array()) {
global $wgRSSFetchTimeout, $wgRSSUserAgent, $wgRSSProxy;
if ( $this->etag ) {
wfDebugLog( 'RSS', 'Used etag: ' . $this->etag );
$headers['If-None-Match'] = $this->etag;
}
if ( $this->lastModified ) {
$lm = gmdate( 'r', $this->lastModified );
wfDebugLog( 'RSS', "Used last modified: $lm" );
$headers['If-Modified-Since'] = $lm;
}
$client = HttpRequest::factory( $this->url, array(
'timeout' => $wgRSSFetchTimeout,
'proxy' => $wgRSSProxy
) );
$client->setUserAgent( $wgRSSUserAgent );
foreach ( $headers as $header => $value ) {
$client->setHeader( $header, $value );
}
$fetch = $client->execute();
$this->client = $client;
if ( !$fetch->isGood() ) {
wfDebug( 'RSS', 'Request Failed: ' . $fetch->getWikiText() );
return $fetch;
}
$ret = $this->responseToXML( $key );
return $ret;
}
/**
* Render the entire feed so that each item is passed to the
* template which the MediaWiki then displays.
*
* @param $parser the parser param to pass to recursiveTagParse()
* @param $frame the frame param to pass to recursiveTagParse()
*/
function renderFeed( $parser, $frame ) {
$renderedFeed = '';
if ( isset( $this->itemTemplate ) && isset( $parser ) && isset( $frame ) ) {
$headcnt = 0;
if ( $this->reversed ) {
$this->rss->items = array_reverse( $this->rss->items );
}
foreach ( $this->rss->items as $item ) {
if ( $this->maxheads > 0 && $headcnt >= $this->maxheads ) {
continue;
}
if ( $this->canDisplay( $item ) ) {
$renderedFeed .= $this->renderItem( $item ) . "\n";
$headcnt++;
}
}
$renderedFeed = $parser->recursiveTagParse( $renderedFeed, $frame );
}
return $renderedFeed;
}
/**
* Render each item, filtering it out if necessary, applying any highlighting.
*
* @param $item Array: an array produced by RSSData where keys are the names of the RSS elements
*/
protected function renderItem( $item ) {
$renderedItem = $this->itemTemplate;
// $info will only be an XML element name, so we're safe using it.
// $item[$info] is handled by the XML parser --
// and that means bad RSS with stuff like
// will find its
// rogue