r112155 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r112154‎ | r112155 | r112156 >
Date:22:13, 22 February 2012
Author:wikinaut
Status:deferred (Comments)
Tags:
Comment:
rewritten into 4-file version. Introducing the new class WikiArticleFeeds. Added the pagetitle to the auto-discovery feed links (meta tag)
Modified paths:
  • /trunk/extensions/WikiArticleFeeds/WikiArticleFeeds.php (modified) (history)
  • /trunk/extensions/WikiArticleFeeds/WikiArticleFeeds_body.php (added) (history)

Diff [purge]

Index: trunk/extensions/WikiArticleFeeds/WikiArticleFeeds_body.php
@@ -0,0 +1,469 @@
 2+<?php
 3+
 4+class WikiArticleFeeds{
 5+
 6+ function feedStart( $text, $params = array() ) {
 7+ return '<!-- FEED_START -->';
 8+ }
 9+
 10+ function feedEnd( $text, $params = array() ) {
 11+ return '<!-- FEED_END -->';
 12+ }
 13+
 14+ function burnFeed( $text, $params = array() ) {
 15+ return ( $params['name'] ? '<!-- BURN_FEED ' . base64_encode( serialize( $params['name'] ) ) . ' -->':'' );
 16+ }
 17+
 18+ function itemTagsTag( $text, $params = array() ) {
 19+ return ( $text ? '<!-- ITEM_TAGS ' . base64_encode( serialize( $text ) ) . ' -->':'' );
 20+ }
 21+
 22+ function itemTagsFunction( $parser ) {
 23+ $tags = func_get_args();
 24+ array_shift( $tags );
 25+ return ( !empty( $tags ) ? '<pre>@ITEMTAGS@' . base64_encode( serialize( implode( ',', $tags ) ) ) . '@ITEMTAGS@</pre>':'' );
 26+ }
 27+
 28+ function itemTagsPlaceholderCorrections( $parser, &$text ) {
 29+ $text = preg_replace(
 30+ '|<pre>@ITEMTAGS@([0-9a-zA-Z\\+\\/]+=*)@ITEMTAGS@</pre>|',
 31+ '<!-- ITEM_TAGS $1 -->',
 32+ $text
 33+ );
 34+ return true;
 35+ }
 36+
 37+ # Sets up the WikiArticleFeeds Parser hooks
 38+ static function wfWikiArticleFeedsSetup( $parser ) {
 39+ global $wgWikiArticleFeeds;
 40+
 41+ $parser->setHook( 'startFeed', array( $wgWikiArticleFeeds, 'feedStart' ) );
 42+ $parser->setHook( 'endFeed', array( $wgWikiArticleFeeds, 'feedEnd' ) );
 43+ $parser->setHook( 'feedBurner', array( $wgWikiArticleFeeds, 'burnFeed' ) );
 44+ $parser->setHook( 'itemTags', array( $wgWikiArticleFeeds, 'itemTagsTag' ) );
 45+ $parser->setFunctionHook( 'itemtags', array( $wgWikiArticleFeeds, 'itemTagsFunction' ) );
 46+
 47+ return true;
 48+ }
 49+
 50+ /**
 51+ * Adds the Wiki feed link headers.
 52+ *
 53+ * Usage: $wgHooks['OutputPageBeforeHTML'][] = 'wfAddWikiFeedHeaders';
 54+ * @param $out Handle to an OutputPage object (presumably $wgOut).
 55+ * @param $text Article/Output text.
 56+ */
 57+ static function wfAddWikiFeedHeaders( $out, $text ) {
 58+ global $wgTitle;
 59+
 60+ # Short-circuit if this article contains no feeds
 61+ if ( !preg_match( '/<!-- FEED_START -->/m', $text ) ) return true;
 62+
 63+ $rssArr = array(
 64+ 'rel' => 'alternate',
 65+ 'type' => 'application/rss+xml',
 66+ 'title' => $wgTitle->getText() . ' - RSS 2.0',
 67+ );
 68+ $atomArr = array(
 69+ 'rel' => 'alternate',
 70+ 'type' => 'application/atom+xml',
 71+ 'title' => $wgTitle->getText() . ' - Atom 0.3',
 72+ );
 73+
 74+ # Test for feedBurner presence
 75+ if ( preg_match( '/<!-- BURN_FEED ([0-9a-zA-Z\\+\\/]+=*) -->/m', $text, $matches ) ) {
 76+ $name = @unserialize( @base64_decode( $matches[1] ) );
 77+ if ( $name ) {
 78+ $rssArr['href'] = 'http://feeds.feedburner.com/' . urlencode( $name ) . '?format=xml';
 79+ $atomArr['href'] = 'http://feeds.feedburner.com/' . urlencode( $name ) . '?format=xml';
 80+ }
 81+ }
 82+
 83+ # If its not being fed by feedBurner, do it ourselves!
 84+ if ( !array_key_exists( 'href', $rssArr ) || !$rssArr['href'] ) {
 85+
 86+ global $wgServer, $wgScript;
 87+
 88+ $baseUrl = $wgServer . $wgScript . '?title=' . $out->getTitle()->getDBkey() . '&action=feed&feed=';
 89+ $rssArr['href'] = $baseUrl . 'rss';
 90+ $atomArr['href'] = $baseUrl . 'atom';
 91+ }
 92+
 93+ $out->addLink( $rssArr );
 94+ $out->addLink( $atomArr );
 95+
 96+ global $wgWikiFeedPresent;
 97+ $wgWikiFeedPresent = true;
 98+
 99+ # True to indicate that other action handlers should continue to process this page
 100+ return true;
 101+ }
 102+
 103+ /**
 104+ * Add additional attributes to links to User- or User_talk pages.
 105+ * These are used later in wfGenerateWikiFeed to determine signatures with timestamps
 106+ * for attributing author and timestamp values to the feed item.
 107+ *
 108+ * Currently this method works only if the user page exists.
 109+ */
 110+ # https://www.mediawiki.org/wiki/Manual:Hooks/LinkEnd
 111+ static function wfWikiArticleFeedsAddSignatureMarker( $skin, $title, $options, $text, &$attribs, $ret ) {
 112+ global $wgWikiFeedPresent;
 113+ // wfDebug( "wfWikiArticleFeedsAddSignatureMarker: Text:". $title->getText() . " Namespace:".$title->getNamespace(). " exists:". $title->exists() . "\n" );
 114+ if ( $title->getNamespace() == NS_USER) {
 115+ $attribs['userpage-link'] = 'true';
 116+ } elseif ( $title->getNamespace() == NS_USER_TALK ) {
 117+ $attribs['usertalkpage-link'] = 'true';
 118+ }
 119+ return true;
 120+ }
 121+
 122+ /**
 123+ * Adds the Wiki feed links to the bottom of the toolbox in Monobook or like-minded skins.
 124+ *
 125+ * Usage: $wgHooks['SkinTemplateToolboxEnd'][] = 'wfWikiArticleFeedsToolboxLinks';
 126+ * @param QuickTemplate $template Instance of MonoBookTemplate or other QuickTemplate
 127+ */
 128+ static function wfWikiArticleFeedsToolboxLinks( $template ) {
 129+ global $wgServer, $wgScript, $wgWikiFeedPresent;
 130+
 131+ # Short-circuit if there are no Feeds present
 132+ if ( !$wgWikiFeedPresent ) {
 133+ return true;
 134+ }
 135+
 136+ if ( is_callable( $template, 'getSkin' ) ) {
 137+ $title = $template->getSkin()->getTitle();
 138+ } else {
 139+ global $wgTitle;
 140+ $title = $wgTitle;
 141+ }
 142+
 143+ if ( $title->getNamespace() < NS_MAIN ) {
 144+ return true;
 145+ }
 146+
 147+ $result = '<li id="feedlinks">';
 148+
 149+ # Test for feedBurner presence
 150+ $burned = false;
 151+ ob_start();
 152+ $template->html( 'bodytext' );
 153+ $text = ob_get_contents();
 154+ ob_end_clean();
 155+ if ( preg_match( '/<!-- BURN_FEED ([0-9a-zA-Z\\+\\/]+=*) -->/m', $text, $matches ) ) {
 156+ $feedBurnerName = @unserialize( @base64_decode( $matches[1] ) );
 157+ if ( $feedBurnerName ) {
 158+ $feeds = array( 'rss' => 'RSS', 'atom' => 'Atom' );
 159+ foreach ( $feeds as $feed => $name ) {
 160+ $result .=
 161+ '<span id="feed-' . htmlspecialchars( $feed ) . '">' .
 162+ '<a href="http://feeds.feedburner.com/' . urlencode( $feedBurnerName ) . '?format=xml">' .
 163+ htmlspecialchars( $name ) . '</a>&#160;</span>';
 164+ }
 165+ $burned = true;
 166+ }
 167+ }
 168+
 169+ # Generate regular RSS and Atom feeds if not fed by feedBurner
 170+ if ( !$burned ) {
 171+ $dbKey = $title->getPrefixedDBkey();
 172+ $baseUrl = $wgServer . $wgScript . '?title=' . $dbKey . '&action=feed&feed=';
 173+ $feeds = array( 'rss' => 'RSS', 'atom' => 'Atom' );
 174+ foreach ( $feeds as $feed => $name ) {
 175+ $result .=
 176+ '<span id="feed-' . htmlspecialchars( $feed ) . '">' .
 177+ '<a href="' . htmlspecialchars( $baseUrl . $feed ) . '">' .
 178+ htmlspecialchars( $name ) . '</a>&#160;</span>';
 179+ }
 180+ }
 181+
 182+ echo ( $result . '</li>' );
 183+
 184+ # True to indicate that other action handlers should continue to process this page
 185+ return true;
 186+ }
 187+
 188+ /**
 189+ * Injects handling of the 'feed' action.
 190+ *
 191+ * Usage: $wgHooks['UnknownAction'][] = 'wfWikiArticleFeedsAction';
 192+ * @param $action Handle to an action string (presumably same as global $action).
 193+ * @param $article Article to be converted to rss or atom feed
 194+ */
 195+ static function wfWikiArticleFeedsAction( $action, $article ) {
 196+
 197+ # If some other action is in the works, cut and run!
 198+ if ( $action != 'feed' ) {
 199+ return true;
 200+ }
 201+
 202+ global $wgOut, $wgRequest, $wgFeedClasses, $wgFeedCacheTimeout, $wgDBname,
 203+ $messageMemc, $wgSitename;
 204+
 205+ # Get query parameters
 206+ $feedFormat = $wgRequest->getVal( 'feed', 'atom' );
 207+ $filterTags = $wgRequest->getVal( 'tags', null );
 208+
 209+ # Process requested tags for use in keys
 210+ if ( $filterTags ) {
 211+ $filterTags = explode( ',', $filterTags );
 212+ array_walk( $filterTags, 'trim' );
 213+ sort( $filterTags );
 214+ }
 215+
 216+ if ( !isset( $wgFeedClasses[$feedFormat] ) ) {
 217+ wfHttpError( 500, "Internal Server Error", "Unsupported feed type." );
 218+ return false;
 219+ }
 220+
 221+ # Setup cache-checking vars
 222+ $title = $article->getTitle();
 223+ $titleDBkey = $title->getPrefixedDBkey();
 224+ $tags = ( is_array( $filterTags ) ? ':' . implode( ',', $filterTags ):'' );
 225+ $key = "{$wgDBname}:wikiarticlefeedsextension:{$titleDBkey}:{$feedFormat}{$tags}";
 226+ $timekey = $key . ":timestamp";
 227+ $cachedFeed = false;
 228+ $feedLastmod = $messageMemc->get( $timekey );
 229+
 230+ # Dermine last modification time for either the article itself or an included template
 231+ $lastmod = $article->getTimestamp();
 232+ $templates = $article->getUsedTemplates();
 233+ foreach ( $templates as $tTitle ) {
 234+ $tArticle = new Article( $tTitle );
 235+ $tmod = $tArticle->getTimestamp();
 236+ $lastmod = ( $lastmod > $tmod ? $lastmod:$tmod );
 237+ }
 238+
 239+ # Check for availability of existing cached **
 240+ if ( ( $wgFeedCacheTimeout > 0 ) && $feedLastmod ) {
 241+ $feedLastmodTS = wfTimestamp( TS_UNIX, $feedLastmod );
 242+ if ( time() - $feedLastmodTS < $wgFeedCacheTimeout
 243+ || $feedLastmodTS > wfTimestamp( TS_UNIX, $lastmod )
 244+ ) {
 245+ wfDebug( "WikiArticleFeedsExtension: Loading feed from cache ($key; $feedLastmod; $lastmod)...\n" );
 246+ $cachedFeed = $messageMemc->get( $key );
 247+ } else {
 248+ wfDebug( "WikiArticleFeedsExtension: Cached feed timestamp check failed ($feedLastmod; $lastmod)\n" );
 249+ }
 250+ }
 251+
 252+ # Display cachedFeed, or generate one from scratch
 253+ global $wgWikiArticleFeedsSkipCache;
 254+ if ( !$wgWikiArticleFeedsSkipCache && is_string( $cachedFeed ) ) {
 255+ wfDebug( "WikiArticleFeedsExtension: Outputting cached feed\n" );
 256+ $feed = new $wgFeedClasses[$feedFormat]( $title->getText(), '', $title->getFullURL(). " - Feed" );
 257+ ob_start();
 258+ $feed->httpHeaders();
 259+ echo $cachedFeed;
 260+ } else {
 261+ wfDebug( "WikiArticleFeedsExtension: Rendering new feed and caching it\n" );
 262+ ob_start();
 263+ WikiArticleFeeds::wfGenerateWikiFeed( $article, $feedFormat, $filterTags );
 264+ $cachedFeed = ob_get_contents();
 265+ ob_end_flush();
 266+
 267+ $expire = 3600; # One hour
 268+ $messageMemc->set( $key, $cachedFeed );
 269+ $messageMemc->set( $timekey, wfTimestamp( TS_MW ), $expire );
 270+ }
 271+
 272+ # False to indicate that other action handlers should not process this page
 273+ return false;
 274+ }
 275+
 276+ /**
 277+ * Purges all associated feeds when an Article is purged.
 278+ *
 279+ * Usage: $wgHooks['ArticlePurge'][] = 'wfPurgeFeedsOnArticlePurge';
 280+ * @param Article $article The Article which is being purged.
 281+ * @return boolean Always true to permit additional hook processing.
 282+ */
 283+ static function wfPurgeFeedsOnArticlePurge( $article ) {
 284+ global $messageMemc, $wgDBname;
 285+ $titleDBKey = $article->mTitle->getPrefixedDBkey();
 286+ $keyPrefix = "{$wgDBname}:wikiarticlefeedsextension:{$titleDBKey}";
 287+ $messageMemc->delete( "{$keyPrefix}:atom:timestamp" );
 288+ $messageMemc->delete( "{$keyPrefix}:atom" );
 289+ $messageMemc->delete( "{$keyPrefix}:rss" );
 290+ $messageMemc->delete( "{$keyPrefix}:rss:timestamp" );
 291+ return true;
 292+ }
 293+
 294+ /**
 295+ * Converts an MediaWiki article into a feed, echoing generated content directly.
 296+ *
 297+ * @param Article $article Article to be converted to RSS or Atom feed.
 298+ * @param String $feedFormat A format type - must be either 'rss' or 'atom'
 299+ * @param Array $filterTags Tags to use in filtering out items.
 300+ */
 301+ static function wfGenerateWikiFeed( $article, $feedFormat = 'atom', $filterTags = null ) {
 302+ global $wgOut, $wgServer, $wgFeedClasses, $wgVersion, $wgSitename;
 303+
 304+ # Setup, handle redirects
 305+ if ( $article->isRedirect() ) {
 306+ $rtitle = Title::newFromRedirect( $article->getContent() );
 307+ if ( $rtitle ) {
 308+ $article = new Article( $rtitle );
 309+ }
 310+ }
 311+ $title = $article->getTitle();
 312+ $feedUrl = $title->getFullUrl();
 313+
 314+ # Parse page into feed items.
 315+ $content = $wgOut->parse( $article->getContent() . "\n__NOEDITSECTION__ __NOTOC__" );
 316+ preg_match_all(
 317+ '/<!--\\s*FEED_START\\s*-->(.*?)<!--\\s*FEED_END\\s*-->/s',
 318+ $content,
 319+ $matches
 320+ );
 321+ $feedContentSections = $matches[1];
 322+
 323+ # Parse and process all feeds, collecting feed items
 324+ $items = array();
 325+ $feedDescription = '';
 326+ foreach ( $feedContentSections as $feedKey => $feedContent ) {
 327+
 328+ # Determine Feed item depth (what header level defines a feed)
 329+ preg_match_all( '/<h(\\d)>/m', $feedContent, $matches );
 330+ if ( !isset( $matches[1] ) ) {
 331+ next;
 332+ }
 333+ $lvl = $matches[1][0];
 334+ foreach ( $matches[1] as $match ) {
 335+ if ( $match < $lvl ) $lvl = $match;
 336+ }
 337+
 338+ $sectionRegExp = '#<h' . $lvl . '>\s*<span.+?id="(.*?)">\s*(.*?)\s*</span>\s*</h' . $lvl . '>#m';
 339+
 340+ # Determine the item titles and default item links
 341+ preg_match_all(
 342+ $sectionRegExp,
 343+ $feedContent,
 344+ $matches
 345+ );
 346+ $itemLinks = $matches[1];
 347+ $itemTitles = $matches[2];
 348+
 349+ # Split content into segments
 350+ $segments = preg_split( $sectionRegExp, $feedContent );
 351+ $segDesc = trim( strip_tags( array_shift( $segments ) ) );
 352+ if ( $segDesc ) {
 353+ if ( !$feedDescription ) {
 354+ $feedDescription = $segDesc;
 355+ } else {
 356+ $feedDescription = wfMsg( 'wikiarticlefeeds_combined_description' );
 357+ }
 358+ }
 359+
 360+ # Loop over parsed segments and add all items to item array
 361+ foreach ( $segments as $key => $seg ) {
 362+
 363+ # Filter by tag (if any are present)
 364+ $skip = false;
 365+ $tags = null;
 366+ if ( is_array( $filterTags ) && !empty( $filterTags ) ) {
 367+ if ( preg_match_all( '/<!-- ITEM_TAGS ([0-9a-zA-Z\\+\\/]+=*) -->/m', $seg, $matches ) ) {
 368+ $tags = array();
 369+ foreach ( $matches[1] as $encodedString ) {
 370+ $t = @unserialize( @base64_decode( $encodedString ) );
 371+ if ( $t ) {
 372+ $t = explode( ',', $t );
 373+ array_walk( $t, 'trim' );
 374+ sort( $t );
 375+ $tags = array_merge( $tags, $t );
 376+ }
 377+ }
 378+ $tags = array_unique( $tags );
 379+ if ( !count( array_intersect( $tags, $filterTags ) ) ) {
 380+ $skip = true;
 381+ }
 382+ $seg = preg_replace( '/<!-- ITEM_TAGS ([0-9a-zA-Z\\+\\/]+=*) -->/m', '', $seg );
 383+ } else {
 384+ $skip = true;
 385+ }
 386+ }
 387+ if ( $skip ) continue;
 388+
 389+ # Try hard to determine the item author and date
 390+ # Look for a regular signatures of the layout
 391+ # userpage-link [optional user_talk page link] date (with a delimiting timezone code in parentheses)
 392+
 393+ $author = null;
 394+ $date = null;
 395+
 396+ $signatureRegExp = '#<a href=".+?User:.+?" title="User:.+?">(.*?)</a> (\d\d):(\d\d), (\d+) ([a-z]+) (\d{4}) (\([A-Z]+\))#im';
 397+
 398+ $signatureRegExp1 = '#<.*userpage-link.*>(.*?)</a>.*<.*usertalkpage-link.*>.*</a>\) (.*\([A-Z]+\))#im';
 399+ $signatureRegExp2 = '#<.*userpage-link.*>(.*?)</a> (.*\([A-Z]+\))#im';
 400+ $signatureRegExp3 = '#<.*usertalkpage-link.*>(.*?)</a> (.*\([A-Z]+\))#im';
 401+
 402+ $isAttributable = ( preg_match( $signatureRegExp1, $seg, $matches )
 403+ || preg_match( $signatureRegExp2, $seg, $matches )
 404+ || preg_match( $signatureRegExp3, $seg, $matches ) );
 405+
 406+ if ( $isAttributable ) {
 407+
 408+ list( $author, $timestring ) = array_slice( $matches, 1 );
 409+
 410+ $tempTimezone = date_default_timezone_get();
 411+ date_default_timezone_set( 'UTC' );
 412+ $date = date( "YmdHis" , strtotime( $timestring ) );
 413+ date_default_timezone_set( $tempTimezone );
 414+
 415+ }
 416+
 417+ # Set default 'article section' feed-link
 418+ $url = $feedUrl . '#' . $itemLinks[$key];
 419+
 420+ # Look for an alternative to the default link (unless default 'section linking' has been forced)
 421+ global $wgForceArticleFeedSectionLinks;
 422+ if ( !$wgForceArticleFeedSectionLinks ) {
 423+ $strippedSeg = preg_replace($signatureRegExp, '', $seg );
 424+ preg_match(
 425+ '#<a [^>]*href=([\'"])(.*?)\\1[^>]*>(.*?)</a>#m',
 426+ $strippedSeg,
 427+ $matches
 428+ );
 429+ if ( isset( $matches[2] ) ) {
 430+ $url = $matches[2];
 431+ if ( preg_match( '#^/#', $url ) ) {
 432+ $url = $wgServer . $url;
 433+ }
 434+ }
 435+ }
 436+
 437+ # Create 'absolutified' segment - where all URLs are fully qualified
 438+ $seg = preg_replace( '/ (href|src)=([\'"])\\//', ' $1=$2' . $wgServer . '/', $seg );
 439+
 440+ # Create item and push onto item list
 441+ $items[$date][] = new FeedItem( strip_tags( $itemTitles[$key] ), $seg, $url, $date, $author );
 442+ }
 443+ }
 444+
 445+ # Programmatically determine the feed title and id.
 446+ $feedTitle = $title->getPrefixedText() . " - Feed";
 447+ $feedId = $title->getFullUrl();
 448+
 449+ # Create feed
 450+ $feed = new $wgFeedClasses[$feedFormat]( $feedTitle, $feedDescription, $feedId );
 451+
 452+ # Push feed header
 453+ $tempWgVersion = $wgVersion;
 454+ $wgVersion .= ' via WikiArticleFeeds ' . VERSION;
 455+ $feed->outHeader();
 456+ $wgVersion = $tempWgVersion;
 457+
 458+ # Sort all items by date and push onto feed
 459+ krsort( $items );
 460+ foreach ( $items as $itemGroup ) {
 461+ foreach ( $itemGroup as $item ) {
 462+ $feed->outItem( $item );
 463+ }
 464+ }
 465+
 466+ # Feed footer
 467+ $feed->outFooter();
 468+ }
 469+
 470+} /* class WikiArticleFeeds */
Property changes on: trunk/extensions/WikiArticleFeeds/WikiArticleFeeds_body.php
___________________________________________________________________
Added: svn:eol-style
1471 + native
Index: trunk/extensions/WikiArticleFeeds/WikiArticleFeeds.php
@@ -1,11 +1,12 @@
22 <?php
33 /**
4 - * WikiArticleFeeds.php - A MediaWiki extension for converting regular pages into feeds.
 4+ * WikiArticleFeeds.php - a MediaWiki extension for converting regular pages into feeds.
55 * @author Jim R. Wilson, Thomas Gries
66 * @maintainer Thomas Gries
77 *
8 - * @version 0.690
 8+ * @version 0.700
99 * @copyright Copyright (C) 2007 Jim R. Wilson
 10+ * @copyright Copyright (C) 2012 Thomas Gries
1011 * @license The MIT License - http://www.opensource.org/licenses/mit-license.php
1112 *
1213 * Description
@@ -15,13 +16,13 @@
1617 *
1718 * Requirements
1819 *
19 - * MediaWiki 1.12.x or higher
20 - * PHP 4.x, 5.x or higher
 20+ * MediaWiki 1.19 or higher
 21+ * PHP 5.x or higher
2122 *
2223 * Installation
2324 *
2425 * 1. Drop this script (WikiArticleFeeds.php) in $IP/extensions
25 - * Note: $IP is your MediaWiki install dir.
 26+ * Note: $IP is your MediaWiki install directory.
2627 * 2. Enable the extension by adding this line to your LocalSettings.php:
2728 * require_once('extensions/WikiArticleFeeds.php');
2829 *
@@ -30,8 +31,11 @@
3132 * Once installed, you may utilize WikiArticleFeeds by invoking the 'feed' action of an article:
3233 * $wgScript?title=Some_Article&action=feed
3334 *
34 - * Note: You may optionally supply a feed type. Acceptable values inculde 'rss' and 'atom'.
35 - * If no feed type is specified, the default is 'atom'. For example:
 35+ * You may optionally supply a feed type.
 36+ * Acceptable values include 'rss' and 'atom'.
 37+ * If no feed type is specified, the default is 'atom'.
 38+ *
 39+ * Example:
3640 * $wgScript?title=Some_Article&action=feed&feed=atom
3741 *
3842 * Creating a Feed
@@ -52,6 +56,8 @@
5357 *
5458 * Versions
5559 *
 60+ * 0.700 rewritten into a four-file version with class
 61+ * auto-discovery rss feedlinks come with the page title in it
5662 * 0.672 changed certain !empty() to isset()
5763 * 0.671 added ob_start() to prevent headers-already sent problem
5864 * added check for undefined variable
@@ -116,7 +122,7 @@
117123 die( "This is not a valid entry point.\n" );
118124 }
119125
120 -define( 'WIKIARTICLEFEEDS_VERSION', '0.690 20120219' );
 126+define( 'VERSION', '0.700 20120222' );
121127
122128 # Bring in supporting classes
123129 require_once( "$IP/includes/Feed.php" );
@@ -126,484 +132,27 @@
127133 $wgExtensionCredits['specialpage'][] = array(
128134 'path' => __FILE__,
129135 'name' => 'WikiArticleFeeds',
130 - 'author' => array( 'Jim Wilson (wilson.jim.r&lt;at&gt;gmail.com)', 'Thomas Gries' ),
 136+ 'author' => array( 'Jim Wilson', 'Thomas Gries' ),
131137 'url' => '//www.mediawiki.org/wiki/Extension:WikiArticleFeeds',
132138 'descriptionmsg' => 'wikiarticlefeeds-desc',
133 - 'version' => WIKIARTICLEFEEDS_VERSION
 139+ 'version' => VERSION,
134140 );
135141
136142 $dir = dirname( __FILE__ ) . '/';
137143 $wgExtensionMessagesFiles['WikiArticleFeeds'] = $dir . 'WikiArticleFeeds.i18n.php';
138144 $wgExtensionMessagesFiles['WikiArticleFeedsMagic'] = $dir . 'WikiArticleFeeds.i18n.magic.php';
139145
140 -/**
141 - * Wrapper class for consolidating all WikiArticleFeeds related parser methods
142 - */
143 -class WikiArticleFeedsParser {
144 - function feedStart( $text, $params = array() ) {
145 - return '<!-- FEED_START -->';
146 - }
147146
148 - function feedEnd( $text, $params = array() ) {
149 - return '<!-- FEED_END -->';
150 - }
151 -
152 - function burnFeed( $text, $params = array() ) {
153 - return ( $params['name'] ? '<!-- BURN_FEED ' . base64_encode( serialize( $params['name'] ) ) . ' -->':'' );
154 - }
155 -
156 - function itemTagsTag( $text, $params = array() ) {
157 - return ( $text ? '<!-- ITEM_TAGS ' . base64_encode( serialize( $text ) ) . ' -->':'' );
158 - }
159 -
160 - function itemTagsFunction( $parser ) {
161 - $tags = func_get_args();
162 - array_shift( $tags );
163 - return ( !empty( $tags ) ? '<pre>@ITEMTAGS@' . base64_encode( serialize( implode( ',', $tags ) ) ) . '@ITEMTAGS@</pre>':'' );
164 - }
165 -
166 - function itemTagsPlaceholderCorrections( $parser, &$text ) {
167 - $text = preg_replace(
168 - '|<pre>@ITEMTAGS@([0-9a-zA-Z\\+\\/]+=*)@ITEMTAGS@</pre>|',
169 - '<!-- ITEM_TAGS $1 -->',
170 - $text
171 - );
172 - return true;
173 - }
174 -}
175 -
176147 # Create global instance
177 -$wgWikiArticleFeedsParser = new WikiArticleFeedsParser();
178 -$wgHooks['ParserBeforeTidy'][] = array( $wgWikiArticleFeedsParser, 'itemTagsPlaceholderCorrections' );
 148+$wgAutoloadClasses['WikiArticleFeeds'] = $dir . 'WikiArticleFeeds_body.php';
179149
180 -# Add Extension Functions
181 -$wgHooks['ParserFirstCallInit'][] = 'wfWikiArticleFeedsParserSetup';
182 -
183 -# Sets up the WikiArticleFeeds Parser hooks
184 -function wfWikiArticleFeedsParserSetup( $parser ) {
185 - global $wgWikiArticleFeedsParser;
186 -
187 - $parser->setHook( 'startFeed', array( $wgWikiArticleFeedsParser, 'feedStart' ) );
188 - $parser->setHook( 'endFeed', array( $wgWikiArticleFeedsParser, 'feedEnd' ) );
189 - $parser->setHook( 'feedBurner', array( $wgWikiArticleFeedsParser, 'burnFeed' ) );
190 - $parser->setHook( 'itemTags', array( $wgWikiArticleFeedsParser, 'itemTagsTag' ) );
191 -
192 - $parser->setFunctionHook( 'itemtags', array( $wgWikiArticleFeedsParser, 'itemTagsFunction' ) );
193 - return true;
194 -}
195 -
196150 # Attach Hooks
197 -$wgHooks['OutputPageBeforeHTML'][] = 'wfAddWikiFeedHeaders';
198 -$wgHooks['SkinTemplateToolboxEnd'][] = 'wfWikiArticleFeedsToolboxLinks'; // introduced in 1.13
199 -$wgHooks['LinkEnd'][] = 'wfWikiArticleFeedsAddSignatureMarker';
200 -$wgHooks['UnknownAction'][] = 'wfWikiArticleFeedsAction';
201 -$wgHooks['ArticlePurge'][] = 'wfPurgeFeedsOnArticlePurge';
 151+$wgHooks['ParserFirstCallInit'][] = 'WikiArticleFeeds::wfWikiArticleFeedsSetup';
 152+$wgHooks['SkinTemplateToolboxEnd'][] = 'WikiArticleFeeds::wfWikiArticleFeedsToolboxLinks';
 153+$wgHooks['OutputPageBeforeHTML'][] = 'WikiArticleFeeds::wfAddWikiFeedHeaders';
 154+$wgHooks['LinkEnd'][] = 'WikiArticleFeeds::wfWikiArticleFeedsAddSignatureMarker';
 155+$wgHooks['UnknownAction'][] = 'WikiArticleFeeds::wfWikiArticleFeedsAction';
 156+$wgHooks['ArticlePurge'][] = 'WikiArticleFeeds::wfPurgeFeedsOnArticlePurge';
202157
203 -/**
204 - * Adds the Wiki feed link headers.
205 - *
206 - * Usage: $wgHooks['OutputPageBeforeHTML'][] = 'wfAddWikiFeedHeaders';
207 - * @param $out Handle to an OutputPage object (presumably $wgOut).
208 - * @param $text Article/Output text.
209 - */
210 -function wfAddWikiFeedHeaders( $out, $text ) {
211 -
212 - # Short-circuit if this article contains no feeds
213 - if ( !preg_match( '/<!-- FEED_START -->/m', $text ) ) return true;
214 -
215 - $rssArr = array(
216 - 'rel' => 'alternate',
217 - 'type' => 'application/rss+xml',
218 - 'title' => 'RSS 2.0',
219 - );
220 - $atomArr = array(
221 - 'rel' => 'alternate',
222 - 'type' => 'application/atom+xml',
223 - 'title' => 'Atom 0.3',
224 - );
225 -
226 - # Test for feedBurner presence
227 - if ( preg_match( '/<!-- BURN_FEED ([0-9a-zA-Z\\+\\/]+=*) -->/m', $text, $matches ) ) {
228 - $name = @unserialize( @base64_decode( $matches[1] ) );
229 - if ( $name ) {
230 - $rssArr['href'] = 'http://feeds.feedburner.com/' . urlencode( $name ) . '?format=xml';
231 - $atomArr['href'] = 'http://feeds.feedburner.com/' . urlencode( $name ) . '?format=xml';
232 - }
233 - }
234 -
235 - # If its not being fed by feedBurner, do it ourselves!
236 - if ( !array_key_exists( 'href', $rssArr ) || !$rssArr['href'] ) {
237 -
238 - global $wgServer, $wgScript;
239 -
240 - $baseUrl = $wgServer . $wgScript . '?title=' . $out->getTitle()->getDBkey() . '&action=feed&feed=';
241 - $rssArr['href'] = $baseUrl . 'rss';
242 - $atomArr['href'] = $baseUrl . 'atom';
243 - }
244 -
245 - $out->addLink( $rssArr );
246 - $out->addLink( $atomArr );
247 -
248 - global $wgWikiFeedPresent;
249 - $wgWikiFeedPresent = true;
250 -
251 - # True to indicate that other action handlers should continue to process this page
252 - return true;
253 -}
254 -
255 -/**
256 - * Add additional attributes to links to User- or User_talk pages.
257 - * These are used later in wfGenerateWikiFeed to determine signatures with timestamps
258 - * for attributing author and timestamp values to the feed item.
259 - *
260 - * Currently this method works only if the user page exists.
261 - */
262 -# https://www.mediawiki.org/wiki/Manual:Hooks/LinkEnd
263 -function wfWikiArticleFeedsAddSignatureMarker( $skin, $title, $options, $text, &$attribs, $ret ) {
264 - if ( $title->getNamespace() == NS_USER) {
265 - $attribs['userpage-link'] = 'true';
266 - } elseif ( $title->getNamespace() == NS_USER_TALK ) {
267 - $attribs['usertalkpage-link'] = 'true';
268 - }
269 - return true;
270 -}
271 -
272 -/**
273 - * Adds the Wiki feed links to the bottom of the toolbox in Monobook or like-minded skins.
274 - *
275 - * Usage: $wgHooks['SkinTemplateToolboxEnd'][] = 'wfWikiArticleFeedsToolboxLinks';
276 - * @param QuickTemplate $template Instance of MonoBookTemplate or other QuickTemplate
277 - */
278 -function wfWikiArticleFeedsToolboxLinks( $template ) {
279 - global $wgServer, $wgScript, $wgWikiFeedPresent;
280 -
281 - # Short-circuit if there are no Feeds present
282 - if ( !$wgWikiFeedPresent ) return true;
283 -
284 - if ( is_callable( $template, 'getSkin' ) ) {
285 - $title = $template->getSkin()->getTitle();
286 - } else {
287 - global $wgTitle;
288 - $title = $wgTitle;
289 - }
290 -
291 - if ( $title->getNamespace() < NS_MAIN ) {
292 - return true;
293 - }
294 -
295 - $result = '<li id="feedlinks">';
296 -
297 - # Test for feedBurner presence
298 - $burned = false;
299 - ob_start();
300 - $template->html( 'bodytext' );
301 - $text = ob_get_contents();
302 - ob_end_clean();
303 - if ( preg_match( '/<!-- BURN_FEED ([0-9a-zA-Z\\+\\/]+=*) -->/m', $text, $matches ) ) {
304 - $feedBurnerName = @unserialize( @base64_decode( $matches[1] ) );
305 - if ( $feedBurnerName ) {
306 - $feeds = array( 'rss' => 'RSS', 'atom' => 'Atom' );
307 - foreach ( $feeds as $feed => $name ) {
308 - $result .=
309 - '<span id="feed-' . htmlspecialchars( $feed ) . '">' .
310 - '<a href="http://feeds.feedburner.com/' . urlencode( $feedBurnerName ) . '?format=xml">' .
311 - htmlspecialchars( $name ) . '</a>&#160;</span>';
312 - }
313 - $burned = true;
314 - }
315 - }
316 -
317 - # Generate regular RSS and Atom feeds if not fed by feedBurner
318 - if ( !$burned ) {
319 - $dbKey = $title->getPrefixedDBkey();
320 - $baseUrl = $wgServer . $wgScript . '?title=' . $dbKey . '&action=feed&feed=';
321 - $feeds = array( 'rss' => 'RSS', 'atom' => 'Atom' );
322 - foreach ( $feeds as $feed => $name ) {
323 - $result .=
324 - '<span id="feed-' . htmlspecialchars( $feed ) . '">' .
325 - '<a href="' . htmlspecialchars( $baseUrl . $feed ) . '">' .
326 - htmlspecialchars( $name ) . '</a>&#160;</span>';
327 - }
328 - }
329 -
330 - echo ( $result . '</li>' );
331 -
332 - # True to indicate that other action handlers should continue to process this page
333 - return true;
334 -}
335 -
336 -/**
337 - * Injects handling of the 'feed' action.
338 - *
339 - * Usage: $wgHooks['UnknownAction'][] = 'wfWikiArticleFeedsAction';
340 - * @param $action Handle to an action string (presumably same as global $action).
341 - * @param $article Article to be converted to rss or atom feed
342 - */
343 -function wfWikiArticleFeedsAction( $action, $article ) {
344 -
345 - # If some other action is in the works, cut and run!
346 - if ( $action != 'feed' ) return true;
347 -
348 - global $wgOut, $wgRequest, $wgFeedClasses, $wgFeedCacheTimeout, $wgDBname, $messageMemc, $wgSitename;
349 -
350 - # Get query parameters
351 - $feedFormat = $wgRequest->getVal( 'feed', 'atom' );
352 - $filterTags = $wgRequest->getVal( 'tags', null );
353 -
354 - # Process requested tags for use in keys
355 - if ( $filterTags ) {
356 - $filterTags = explode( ',', $filterTags );
357 - array_walk( $filterTags, 'trim' );
358 - sort( $filterTags );
359 - }
360 -
361 - if ( !isset( $wgFeedClasses[$feedFormat] ) ) {
362 - wfHttpError( 500, "Internal Server Error", "Unsupported feed type." );
363 - return false;
364 - }
365 -
366 - # Setup cache-checking vars
367 - $title = $article->getTitle();
368 - $titleDBkey = $title->getPrefixedDBkey();
369 - $tags = ( is_array( $filterTags ) ? ':' . implode( ',', $filterTags ):'' );
370 - $key = "{$wgDBname}:wikiarticlefeedsextension:{$titleDBkey}:{$feedFormat}{$tags}";
371 - $timekey = $key . ":timestamp";
372 - $cachedFeed = false;
373 - $feedLastmod = $messageMemc->get( $timekey );
374 -
375 - # Dermine last modification time for either the article itself or an included template
376 - $lastmod = $article->getTimestamp();
377 - $templates = $article->getUsedTemplates();
378 - foreach ( $templates as $tTitle ) {
379 - $tArticle = new Article( $tTitle );
380 - $tmod = $tArticle->getTimestamp();
381 - $lastmod = ( $lastmod > $tmod ? $lastmod:$tmod );
382 - }
383 -
384 - # Check for availability of existing cached version
385 - if ( ( $wgFeedCacheTimeout > 0 ) && $feedLastmod ) {
386 - $feedLastmodTS = wfTimestamp( TS_UNIX, $feedLastmod );
387 - if (
388 - time() - $feedLastmodTS < $wgFeedCacheTimeout ||
389 - $feedLastmodTS > wfTimestamp( TS_UNIX, $lastmod )
390 - ) {
391 - wfDebug( "WikiArticleFeedsExtension: Loading feed from cache ($key; $feedLastmod; $lastmod)...\n" );
392 - $cachedFeed = $messageMemc->get( $key );
393 - } else {
394 - wfDebug( "WikiArticleFeedsExtension: Cached feed timestamp check failed ($feedLastmod; $lastmod)\n" );
395 - }
396 - }
397 -
398 - # Display cachedFeed, or generate one from scratch
399 - global $wgWikiArticleFeedsSkipCache;
400 - if ( !$wgWikiArticleFeedsSkipCache && is_string( $cachedFeed ) ) {
401 - wfDebug( "WikiArticleFeedsExtension: Outputting cached feed\n" );
402 - $feed = new $wgFeedClasses[$feedFormat]( $title->getText(), '', $title->getFullURL(). " - Feed" );
403 - ob_start();
404 - $feed->httpHeaders();
405 - echo $cachedFeed;
406 - } else {
407 - wfDebug( "WikiArticleFeedsExtension: Rendering new feed and caching it\n" );
408 - ob_start();
409 - wfGenerateWikiFeed( $article, $feedFormat, $filterTags );
410 - $cachedFeed = ob_get_contents();
411 - ob_end_flush();
412 -
413 - $expire = 3600; # One hour
414 - $messageMemc->set( $key, $cachedFeed );
415 - $messageMemc->set( $timekey, wfTimestamp( TS_MW ), $expire );
416 - }
417 -
418 - # False to indicate that other action handlers should not process this page
419 - return false;
420 -}
421 -
422 -/**
423 - * Purges all associated feeds when an Article is purged.
424 - *
425 - * Usage: $wgHooks['ArticlePurge'][] = 'wfPurgeFeedsOnArticlePurge';
426 - * @param Article $article The Article which is being purged.
427 - * @return boolean Always true to permit additional hook processing.
428 - */
429 -function wfPurgeFeedsOnArticlePurge( $article ) {
430 - global $messageMemc, $wgDBname;
431 - $titleDBKey = $article->mTitle->getPrefixedDBkey();
432 - $keyPrefix = "{$wgDBname}:wikiarticlefeedsextension:{$titleDBKey}";
433 - $messageMemc->delete( "{$keyPrefix}:atom:timestamp" );
434 - $messageMemc->delete( "{$keyPrefix}:atom" );
435 - $messageMemc->delete( "{$keyPrefix}:rss" );
436 - $messageMemc->delete( "{$keyPrefix}:rss:timestamp" );
437 - return true;
438 -}
439 -
440 -/**
441 - * Converts an MediaWiki article into a feed, echoing generated content directly.
442 - *
443 - * @param Article $article Article to be converted to RSS or Atom feed.
444 - * @param String $feedFormat A format type - must be either 'rss' or 'atom'
445 - * @param Array $filterTags Tags to use in filtering out items.
446 - */
447 -function wfGenerateWikiFeed( $article, $feedFormat = 'atom', $filterTags = null ) {
448 - global $wgOut, $wgServer, $wgFeedClasses, $wgVersion, $wgSitename;
449 -
450 - # Setup, handle redirects
451 - if ( $article->isRedirect() ) {
452 - $rtitle = Title::newFromRedirect( $article->getContent() );
453 - if ( $rtitle ) {
454 - $article = new Article( $rtitle );
455 - }
456 - }
457 - $title = $article->getTitle();
458 - $feedUrl = $title->getFullUrl();
459 -
460 - # Parse page into feed items.
461 - $content = $wgOut->parse( $article->getContent() . "\n__NOEDITSECTION__ __NOTOC__" );
462 - preg_match_all(
463 - '/<!--\\s*FEED_START\\s*-->(.*?)<!--\\s*FEED_END\\s*-->/s',
464 - $content,
465 - $matches
466 - );
467 - $feedContentSections = $matches[1];
468 -
469 - # Parse and process all feeds, collecting feed items
470 - $items = array();
471 - $feedDescription = '';
472 - foreach ( $feedContentSections as $feedKey => $feedContent ) {
473 -
474 - # Determine Feed item depth (what header level defines a feed)
475 - preg_match_all( '/<h(\\d)>/m', $feedContent, $matches );
476 - if ( !isset( $matches[1] ) ) next;
477 - $lvl = $matches[1][0];
478 - foreach ( $matches[1] as $match ) {
479 - if ( $match < $lvl ) $lvl = $match;
480 - }
481 -
482 - $sectionRegExp = '#<h' . $lvl . '>\s*<span.+?id="(.*?)">\s*(.*?)\s*</span>\s*</h' . $lvl . '>#m';
483 -
484 - # Determine the item titles and default item links
485 - preg_match_all(
486 - $sectionRegExp,
487 - $feedContent,
488 - $matches
489 - );
490 - $itemLinks = $matches[1];
491 - $itemTitles = $matches[2];
492 -
493 - # Split content into segments
494 - $segments = preg_split( $sectionRegExp, $feedContent );
495 - $segDesc = trim( strip_tags( array_shift( $segments ) ) );
496 - if ( $segDesc ) {
497 - if ( !$feedDescription ) {
498 - $feedDescription = $segDesc;
499 - } else {
500 - $feedDescription = wfMsg( 'wikiarticlefeeds_combined_description' );
501 - }
502 - }
503 -
504 - # Loop over parsed segments and add all items to item array
505 - foreach ( $segments as $key => $seg ) {
506 -
507 - # Filter by tag (if any are present)
508 - $skip = false;
509 - $tags = null;
510 - if ( is_array( $filterTags ) && !empty( $filterTags ) ) {
511 - if ( preg_match_all( '/<!-- ITEM_TAGS ([0-9a-zA-Z\\+\\/]+=*) -->/m', $seg, $matches ) ) {
512 - $tags = array();
513 - foreach ( $matches[1] as $encodedString ) {
514 - $t = @unserialize( @base64_decode( $encodedString ) );
515 - if ( $t ) {
516 - $t = explode( ',', $t );
517 - array_walk( $t, 'trim' );
518 - sort( $t );
519 - $tags = array_merge( $tags, $t );
520 - }
521 - }
522 - $tags = array_unique( $tags );
523 - if ( !count( array_intersect( $tags, $filterTags ) ) ) $skip = true;
524 - $seg = preg_replace( '/<!-- ITEM_TAGS ([0-9a-zA-Z\\+\\/]+=*) -->/m', '', $seg );
525 - } else {
526 - $skip = true;
527 - }
528 - }
529 - if ( $skip ) continue;
530 -
531 - # Try hard to determine the item author and date
532 - # Look for a regular signatures of the layout
533 - # userpage-link [optional user_talk page link] date (with a delimiting timezone code in parentheses)
534 -
535 - $author = null;
536 - $date = null;
537 -
538 - $signatureRegExp = '#<a href=".+?User:.+?" title="User:.+?">(.*?)</a> (\d\d):(\d\d), (\d+) ([a-z]+) (\d{4}) (\([A-Z]+\))#im';
539 -
540 - $signatureRegExp1 = '#<.*userpage-link.*>(.*?)</a>.*<.*usertalkpage-link.*>.*</a>\) (.*\([A-Z]+\))#im';
541 - $signatureRegExp2 = '#<.*userpage-link.*>(.*?)</a> (.*\([A-Z]+\))#im';
542 - $signatureRegExp3 = '#<.*usertalkpage-link.*>(.*?)</a> (.*\([A-Z]+\))#im';
543 -
544 - $isAttributable = ( preg_match( $signatureRegExp1, $seg, $matches )
545 - || preg_match( $signatureRegExp2, $seg, $matches )
546 - || preg_match( $signatureRegExp3, $seg, $matches ) );
547 -
548 - if ( $isAttributable ) {
549 -
550 - list( $author, $timestring ) = array_slice( $matches, 1 );
551 -
552 - $tempTimezone = date_default_timezone_get();
553 - date_default_timezone_set( 'UTC' );
554 - $date = date( "YmdHis" , strtotime( $timestring ) );
555 - date_default_timezone_set( $tempTimezone );
556 -
557 - }
558 -
559 - # Set default 'article section' feed-link
560 - $url = $feedUrl . '#' . $itemLinks[$key];
561 -
562 - # Look for an alternative to the default link (unless default 'section linking' has been forced)
563 - global $wgForceArticleFeedSectionLinks;
564 - if ( !$wgForceArticleFeedSectionLinks ) {
565 - $strippedSeg = preg_replace($signatureRegExp, '', $seg );
566 - preg_match(
567 - '#<a [^>]*href=([\'"])(.*?)\\1[^>]*>(.*?)</a>#m',
568 - $strippedSeg,
569 - $matches
570 - );
571 - if ( isset( $matches[2] ) ) {
572 - $url = $matches[2];
573 - if ( preg_match( '#^/#', $url ) ) {
574 - $url = $wgServer . $url;
575 - }
576 - }
577 - }
578 -
579 - # Create 'absolutified' segment - where all URLs are fully qualified
580 - $seg = preg_replace( '/ (href|src)=([\'"])\\//', ' $1=$2' . $wgServer . '/', $seg );
581 -
582 - # Create item and push onto item list
583 - $items[$date][] = new FeedItem( strip_tags( $itemTitles[$key] ), $seg, $url, $date, $author );
584 - }
585 - }
586 -
587 - # Programmatically determine the feed title and id.
588 - $feedTitle = $title->getPrefixedText() . " - Feed";
589 - $feedId = $title->getFullUrl();
590 -
591 - # Create feed
592 - $feed = new $wgFeedClasses[$feedFormat]( $feedTitle, $feedDescription, $feedId );
593 -
594 - # Push feed header
595 - $tempWgVersion = $wgVersion;
596 - $wgVersion .= ' via WikiArticleFeeds ' . WIKIARTICLEFEEDS_VERSION;
597 - $feed->outHeader();
598 - $wgVersion = $tempWgVersion;
599 -
600 - # Sort all items by date and push onto feed
601 - krsort( $items );
602 - foreach ( $items as $itemGroup ) {
603 - foreach ( $itemGroup as $item ) {
604 - $feed->outItem( $item );
605 - }
606 - }
607 -
608 - # Feed footer
609 - $feed->outFooter();
610 -}
 158+$wgWikiArticleFeeds = new WikiArticleFeeds();
 159+$wgHooks['ParserBeforeTidy'][] = array( $wgWikiArticleFeeds, 'WikiArticleFeeds::itemTagsPlaceholderCorrections' );

Follow-up revisions

RevisionCommit summaryAuthorDate
r112189changed the extension's version string constant name (back) to a unique value...wikinaut07:47, 23 February 2012

Comments

#Comment by Wikinaut (talk | contribs)   08:19, 23 February 2012

VERSION may not be used, because it is already dsefined. Fixed in r112189 .

Status & tagging log