Index: trunk/extensions/News/News.php
===================================================================
--- trunk/extensions/News/News.php (revision 21282)
+++ trunk/extensions/News/News.php (revision 21283)
@@ -22,19 +22,172 @@
'description' => 'shows recent changes on a wiki page',
);
+$wgNewsFeedURLPattern = false; // pattern for feed-URLs; useful when using rewrites for canonical feed URLs
+$wgNewsFeedUserPattern = false; // pattern to use for the author-field in feed items.
+
$wgExtensionFunctions[] = "wfNewsExtension";
$wgAutoloadClasses['NewsRenderer'] = dirname( __FILE__ ) . '/NewsRenderer.php';
+$wgHooks['ArticleViewHeader'][] = 'wfNewsArticleViewHeader';
+$wgHooks['ArticlePurge'][] = 'wfNewsArticlePurge';
+$wgHooks['SkinTemplateOutputPageBeforeExec'][] = 'wfNewsSkinTemplateOutputPageBeforeExec';
+//FIXME: find a way to override the feed URLs generated by OutputPage::getHeadLinks
+
function wfNewsExtension() {
global $wgParser;
- $wgParser->setHook( "news", "newsxRenderNews" );
+ $wgParser->setHook( "news", "wfNewsTag" );
+ $wgParser->setHook( "newsfeed", "wfNewsFeedTag" );
+ $wgParser->setHook( "newsfeedlink", "wfNewsFeedLinkTag" );
}
+function wfNewsTag( $templatetext, $argv, &$parser ) {
+ global $wgTitle;
-function newsxRenderNews( $templatetext, $argv, &$parser ) {
- $renderer = new NewsRenderer($templatetext, $argv, $parser);
+ $parser->disableCache(); //TODO: use smart cache & purge...?
+ $renderer = new NewsRenderer($wgTitle, $templatetext, $argv, $parser);
+
return $renderer->renderNews();
}
+function wfNewsFeedTag( $templatetext, $argv, &$parser ) {
+ global $wgTitle, $wgOut;
+
+ $parser->disableCache(); //TODO: use smart cache & purge...?
+ $wgOut->setSyndicated( true );
+
+ #$rss = $renderer->renderFeedMetaLink( 'rss' );
+ #$atom = $renderer->renderFeedMetaLink( 'atom' );
+ #$parser->mOutput->addHeadItem($rss . $atom);
+
+ $renderer = new NewsRenderer($wgTitle, $templatetext, $argv, $parser);
+ $html = $renderer->renderFeedPreview();
+ return $html;
+}
+
+function wfNewsFeedLinkTag( $linktext, $argv, &$parser ) {
+ return NewsRenderer::renderFeedLink($linktext, $argv, $parser);
+}
+
+function wfNewsCacheKey( $title, $format ) {
+ //global $wgLang;
+ //NOTE: per-language caching might be needed at some point.
+ // right now, caching is done for anon users only
+ // (the content language might be set individually however,
+ // using an extension like LanguageSelector)
+
+ return "@newsfeed:" . urlencode($title->getPrefixedDBKey()) . '|' . urlencode($format);
+}
+
+function wfNewsArticleViewHeader( &$article ) {
+ global $wgRequest, $wgOut, $wgFeedClasses, $wgUser;
+
+ $format = $wgRequest->getVal( 'feed' );
+ if (!$format) return true;
+
+ $wgOut->disable();
+ //XXX: returning false currently doesn't stop the rest of Article::view to execute :(
+
+ $title = $article->getTitle();
+ $format = strtolower( trim($format) );
+
+ if ( !isset($wgFeedClasses[$format] ) ) {
+ wfHttpError(400, "Bad Request", "unknown feed format: " . $format); //TODO: better code & text
+ return false;
+ }
+
+ if (!$article->exists()) {
+ wfHttpError(404, "Not Found", "feed page not found: " . $title->getPrefixedText()); //TODO: better text
+ return false;
+ }
+
+ $note = '';
+
+ //NOTE: do caching for anon users only, because of user-specific
+ // rendering of textual content
+ if ($wgUser->isAnon()) {
+ $cachekey = wfNewsCacheKey($title, $format);
+ $ocache = wfGetParserCacheStorage();
+ $e = $ocache ? $ocache->get( $cachekey ) : NULL;
+ $note .= ' anon;';
+ }
+ else {
+ $cachekey = NULL;
+ $ocache = NULL;
+ $e = NULL;
+ $note .= ' user;';
+ }
+
+ if ( $e ) {
+ $lastchange = wfTimestamp(TS_UNIX, NewsRenderer::getLastChangeTime());
+ if ($lastchange < $e['timestamp']) {
+ print $e['xml'] . "\n<!-- cached: $note -->\n";
+ return false; //done
+ }
+ else {
+ $note .= " stale: $lastchange >= {$e['timestamp']};";
+ }
+ }
+
+ global $wgParser; //evil global
+
+ if (!$wgParser->mOptions) { //XXX: ugly hack :(
+ $wgParser->mOptions = new ParserOptions;
+ $wgParser->setOutputType( OT_HTML );
+ $wgParser->clearState();
+ $wgParser->mTitle = $title;
+ }
+
+ $renderer = NewsRenderer::newFromArticle( $article, $wgParser );
+ if (!$renderer) {
+ wfHttpError(404, "Bad Request", "no feed found on page: " . $title->getPrefixedText() ); //TODO: better code & text
+ return;
+ }
+
+ $description = ''; //TODO: grab from article content... but what? and how?
+ $ts = time();
+ $xml = $renderer->renderFeed( $format, $description );
+
+ $e = array( 'xml' => $xml, 'timestamp' => $ts );
+ if ($ocache) {
+ $ocache->set( $cachekey, $e, $ts + 24 * 60 * 60 ); //cache for max 24 hours; cached record is discarded when anything turns up in RC anyway.
+ $note .= ' updated;';
+ }
+
+ $wgOut->disable();
+ print $xml . "\n<!-- fresh: $note -->\n";
+ return false; //done
+}
+
+function wfNewsArticlePurge( &$article ) {
+ global $wgFeedClasses;
+
+ $ocache = wfGetParserCacheStorage();
+ if (!$ocache) return true;
+
+ $title = $article->getTitle();
+
+ foreach( $wgFeedClasses as $format => $class ) {
+ $cachekey = wfNewsCacheKey( $title, $format );
+ $ocache->delete( $cachekey );
+ }
+
+ return true;
+}
+
+function wfNewsSkinTemplateOutputPageBeforeExec( &$skin, &$tpl ) {
+ $feeds = $tpl->data['feeds'];
+ if (!$feeds) return true;
+
+ $title = $skin->mTitle; //hack...
+
+ foreach ($feeds as $format => $e) {
+ $e['href'] = NewsRenderer::getFeedURL( $title, $format );
+ $feeds[$format] = $e;
+ }
+
+ $tpl->setRef( 'feeds', $feeds );
+ true;
+}
+
?>
\ No newline at end of file
Index: trunk/extensions/News/NewsRenderer.php
===================================================================
--- trunk/extensions/News/NewsRenderer.php (revision 21282)
+++ trunk/extensions/News/NewsRenderer.php (revision 21283)
@@ -14,6 +14,9 @@
die( 1 );
}
+define('NEWS_HEAD_LENGTH', 1024 * 2);
+define('NEWS_HEAD_SCAN', 256);
+
#no need to include, rely on autoloader
#global $IP;
#require_once( "$IP/includes/RecentChange.php" );
@@ -23,6 +26,11 @@
var $parser;
var $skin;
+ var $title;
+
+ var $prefix;
+ var $postfix;
+
var $usetemplate;
var $templatetext;
var $templateparser;
@@ -42,9 +50,35 @@
var $onlynew;
var $onlypatrolled;
- function __construct( $templatetext, $argv, &$parser ) {
+ static function newFromArticle( &$article, &$parser ) {
+ $title = $article->getTitle();
+ $article->getContent();
+ $text = $article->mContent;
+ if (!$text) return NULL;
+
+ $uniq_prefix = "\x07NR-UNIQ";
+ $elements = array( 'nowiki', 'gallery', 'newsfeed');
+ $matches = array();
+ $text = Parser::extractTagsAndParams( $elements, $text, $matches, $uniq_prefix );
+
+ foreach( $matches as $marker => $data ) {
+ list( $element, $content, $params, $tag ) = $data;
+ $tagName = strtolower( $element );
+
+ if ($tagName != 'newsfeed') continue;
+ #if (!is_null($id) && (!isset($params['id']) || $params['id'] != $id)) continue;
+
+ return new NewsRenderer( $title, $content, $params, $parser );
+ }
+
+ return NULL;
+ }
+
+ function __construct( $title, $templatetext, $argv, &$parser ) {
global $wgContLang, $wgUser;
+ $this->title = $title;
+
$this->skin = $wgUser->getSkin();
$this->parser = $parser;
@@ -62,24 +96,38 @@
#$template = @$argv['template'];
- if ( $this->usetemplate ) {
+ #if ( $this->usetemplate ) {
#print "<pre>$templatetitle</pre>";
- $this->templateparser = clone $parser;
- $this->templateparser->setOutputType( OT_HTML );
-
+ $this->templateparser = $parser;
+ #$this->templateparser = clone $parser;
+ #$this->templateparser->setOutputType( OT_HTML );
+ $this->templateoptions = new ParserOptions;
+ $this->templateoptions->setEditSection( false );
+ $this->templateoptions->setNumberHeadings( false );
+ $this->templateoptions->setRemoveComments( true );
+ $this->templateoptions->setUseTeX( false );
+ $this->templateoptions->setUseDynamicDates( false );
+ $this->templateoptions->setInterwikiMagic( true ); //strip interlanguage-links
+ $this->templateoptions->setAllowSpecialInclusion( false );
+
#$this->templatetitle = Title::newFromText( $template, NS_TEMPLATE );
#$templatetext = $templateparser->fetchTemplate( $templatetitle );
#print "<pre>$templatetext</pre>";
- $this->templateoptions = new ParserOptions;
#$templateoptions->setRemoveComments( true );
#$templateoptions->setMaxIncludeSize( self::MAX_INCLUDE_SIZE );
- }
- else {
+ #}
+
+ if ( !$this->usetemplate ) {
$this->changelist = new OldChangesList( $this->skin );
}
+ #$this->feedId = @$argv['id'];
+
+ $this->prefix = @$argv['prefix'];
+ $this->postfix = @$argv['postfix'];
+
$this->limit = @$argv['limit'];
if ( !$this->limit ) $this->limit = 10;
else if ( $this->limit > 100 ) $this->limit = 100;
@@ -150,9 +198,30 @@
$group[] = 'rc_namespace AND rc_title';
}
*/
-
}
+ /*
+ function getFeedId() {
+ return $this->feedId;
+ }
+ */
+
+ /*
+ function getCacheKey() {
+ return '@' . get_class($this) . ':' .
+ ($this->templatetext ? md5($this->templatetext) : $this->templatetext ). '|' .
+ $this->namespaces . '|' .
+ $this->categories . '|' .
+ $this->types . '|' .
+ $this->nominor . ',' .
+ $this->noanon . ',' .
+ $this->nobot . ',' .
+ $this->notalk . ',' .
+ $this->onlynew . ',' .
+ $this->onlypatrolled ;
+ }
+ */
+
function query( $dbr, $limit, $offset = 0 ) {
list( $trecentchanges, $tpage, $tcategorylinks ) = $dbr->tableNamesN( 'recentchanges', 'page', 'categorylinks' );
@@ -191,24 +260,15 @@
$sql = $dbr->limitResult( $sql, $limit, $offset );
- $res = $dbr->query( $sql, 'newsxFetchRows' );
+ $res = $dbr->query( $sql, 'NewsRenderer::query' );
return $res;
}
- # The callback function for converting the input text to HTML output
- function renderNews( ) {
- global $wgTitle;
-
- $this->parser->disableCache();
-
+ function fetchNews( ) {
$dbr = wfGetDB( DB_SLAVE );
+ $news = array();
- $text = '';
-
- if ( !$this->usetemplate )
- $text .= $this->changelist->beginRecentChangesList();
-
$remaining = $this->limit;
$offset = 0;
$ignore = array(); #collect stuff we already have, when in unique mode
@@ -228,8 +288,7 @@
$ignore[$k] = true;
}
- $t = $this->renderRow( $row );
- $text .= trim($t) . "\n"; #FIXME: handle blank lines at the end sanely. Paragraphs may be desired, but not when using lists.
+ $news[] = $row;
$remaining -= 1;
}
@@ -238,9 +297,32 @@
if ( !$has ) break; #empty result set, stop trying
}
+ return $news;
+ }
+
+ function renderNews( ) {
+ global $wgTitle;
+
+ $news = $this->fetchNews();
+
+ $text = '';
+
+ if ( $this->usetemplate ) {
+ $text .= $this->prefix;
+ }
+ else {
+ $text .= $this->changelist->beginRecentChangesList();
+ }
+
+ foreach ($news as $row) {
+ $t = $this->renderRow( $row );
+ $text .= trim($t) . "\n"; #TODO: handle blank lines at the end sanely. Paragraphs may be desired, but not when using lists.
+ }
+
if ( $this->usetemplate ) { #it's wikitext, parse
- $output = $this->templateparser->parse( $text, $wgTitle, $this->templateoptions, true );
- $html = $output->getText();
+ #$output = $this->templateparser->parse( $text, $wgTitle, $this->templateoptions, true );
+ $text .= $this->postfix;
+ $html = $this->templateparser->recursiveTagParse( $text );
}
else { #it's already html
$text .= $this->changelist->endRecentChangesList();
@@ -249,14 +331,130 @@
return $html;
}
+
+ function renderFeed( $format, $description = '' ) {
+ global $wgSitename, $wgFeedClasses;
+ $date = wfTimestamp(); //XXX: use MAX(rc_timestamp) ?
+
+ $cls = $wgFeedClasses[$format];
+ if (!class_exists($cls)) return false;
+
+ $url = $this->title->getFullUrl();
+ $feed = new $cls( $this->title->getText() . ' - ' . $wgSitename , $description, $url, $date );
+
+ $news = $this->fetchNews();
+
+ ob_start();
+ $feed->outHeader();
+ foreach ($news as $row) {
+ $t = $this->renderRow( $row, true );
+ $item = $this->makeFeedItem( $row, $t, true );
+
+ $feed->outItem( $item );
+ }
+
+ $feed->outFooter();
+ $xml = ob_get_contents();
+ ob_end_clean();
+
+ return $xml;
+ }
- function renderRow( $row ) {
+ function renderFeedPreview( ) {
+ $news = $this->fetchNews();
+
+ $html = '';
+
+ foreach ($news as $row) {
+ $t = $this->renderRow( $row, true );
+ $item = $this->makeFeedItem( $row, $t, false );
+ $t = $this->renderFeedItem( $item );
+ $html .= $t;
+ }
+
+ return $html;
+ }
+
+ function renderFeedItem( $item ) {
+ global $wgContLang, $wgUser;
+ $sk = $wgUser->getSkin();
+
+ $html = '';
+ $html .= '<div class="newsfeed-item">';
+ $html .= '<div class="newsfeed-item-head">';
+
+ $html .= '<h1>' . $sk->makeKnownLinkObj( $item->title_object ) . '</h1>';
+
+ $html .= '<p><small>';
+ $html .= $item->getAuthor();
+ $html .= ', ';
+ $html .= $wgContLang->timeanddate( $item->getDate() );
+ if ( $item->getComments() ) {
+ $html .= ' - <i>';
+ $html .= htmlspecialchars( $item->raw_comment );
+ $html .= '</i>';
+ }
+ $html .= '</small></p>';
+
+ $html .= '</div>';
+
+ $html .= '<div class="newsfeed-item-content">';
+ $html .= $item->raw_text;
+ $html .= '</div>';
+ $html .= '</div>';
+ return $html;
+ }
+
+ function makeFeedItem( $row, $text, $standalone ) {
+ global $wgNewsFeedUserPattern;
+
+ $text = $text . ' __NOTOC__'; #XXX ugly hack!
+
+ if ($standalone) {
+ $output = $this->templateparser->parse( $text, $GLOBALS['wgTitle'], $this->templateoptions, true );
+ $text = $output->mText;
+ }
+ else { //FIXME: mask interwikis, categories, etc!!!!!!!!
+ $text = $this->templateparser->recursiveTagParse( $text );
+ }
+
+ if ( $wgNewsFeedUserPattern ) {
+ $user = str_replace('$1', $row->rc_user_text, $wgNewsFeedUserPattern);
+ }
+ else {
+ $user = $row->rc_user_text;
+ }
+
+ $title = Title::makeTitle( $row->rc_namespace, $row->rc_title ); //XXX: this is redundant, we already have a title object in renderRow. But no good way to pass it :(
+ $item = new FeedItem( $title->getPrefixedText(),
+ $text,
+ $title->getFullURL(),
+ $row->rc_timestamp,
+ $user,
+ $row->rc_comment );
+
+ //XXX: ugly hack - things used by preview
+ $item->raw_text = $text; //needed because FeedItem holds text html-encoded internally. wtf
+ $item->raw_comment = $row->rc_comment; //needed because FeedItem holds text html-encoded internally. wtf
+ $item->title_object = $title; //title object
+ return $item;
+ }
+
+ function renderRow( $row, $forFeed = false ) {
global $wgUser, $wgLang;
-
+
$change = RecentChange::newFromRow( $row );
$change->counter = 0; //hack
-
- if ( !$this->usetemplate ) {
+
+ $usetemplate = $this->usetemplate;
+ $templatetext = $this->templatetext;
+
+ if (!$templatetext && $forFeed) {
+ $templatetext = '{{{head}}}';
+ $usetemplate = true;
+ }
+
+ if ( !$usetemplate ) {
#$pagelink = $this->skin->makeKnownLinkObj( $title );
$this->changelist->insertDateHeader($dummy, $row->rc_timestamp); #dummy call to suppress date headers
@@ -276,7 +474,7 @@
$params['minor'] = $row->rc_minor ? 'true' : '';
$params['bot'] = $row->rc_bot ? 'true' : '';
$params['patrolled'] = $row->rc_patrolled ? 'true' : '';
- $params['anon'] = ( $row->rc_user <= 0 ) ? 'true' : ''; #TODO: perhaps use (rc_user == rc_ip) instead? That would take care of entries from importing.
+ $params['anon'] = ( $row->rc_user <= 0 ) ? 'true' : ''; #XXX: perhaps use (rc_user == rc_ip) instead? That would take care of entries from importing.
$params['new'] = ( $row->rc_type == RC_NEW ) ? 'true' : '';
$params['type'] = $row->rc_type;
@@ -300,11 +498,184 @@
$params['permalink'] = $permaq ? $title->getFullURL( $permaq ) : '';
$params['comment'] = str_replace( array( '{{', '}}', '|', '\'' ), array( '{{', '}}', '|', '$#39;' ), wfEscapeWikiText( $row->rc_comment ) );
-
- $text = $this->templateparser->replaceVariables( $this->templatetext, $params );
+
+ if ( stripos($templatetext, '{{{content}}}')!==false || stripos($templatetext, '{{{head}}}')!==false ) {
+ $article = new Article( $title, $row->rc_this_oldid );
+ $t = $article->getContent();
+
+ $params['content'] = NewsRenderer::sanitizeWikiText( $t );
+ //TODO: expand variables & templates first, so cut-off applies to effective content,
+ // and extension tags from templates are stripped properly
+ //TODO: avoid magic categories, interwiki-links, etc
+
+ if ( stripos($templatetext, '{{{head}}}')!==false ) {
+ $params['head'] = NewsRenderer::extractHead( $params['content'], $title );
+ }
+ }
+
+ $text = $this->templateparser->replaceVariables( $templatetext, $params );
return $text;
}
}
+
+ /*
+ function renderFeedMetaLink( $format ) {
+ $format = strtolower(trim($format));
+
+ $name = $format;
+ if ($name == 'rss') $name = 'RSS 2.0';
+ else if ($name == 'atom') $name = 'Atom 1.0';
+
+ $mime = "application/$format+xml"; //hack
+ $url = NewsRenderer::getFeedURL($this->title, $format);
+ #$id = $this->feedId ? htmlspecialchars($this->feedId) : NULL;
+
+ $html = '<link rel="alternate" type="'.$mime.'" title="'.($id?"$id - ":'').$name.'" href="'.htmlspecialchars($url).'">';
+ return $html;
+ }
+ */
+
+ static function getFeedURL( $title, $format ) {
+ global $wgNewsFeedURLPattern;
+
+ if ( $wgNewsFeedURLPattern ) {
+ $params = array(
+ '$1' => urlencode( $title->getPrefixedDBKey() ),
+ '$2' => urlencode( $format ),
+ #'$3' => urlencode( $feedId ),
+ );
+
+ $url = str_replace(array_keys($params), array_values($params), $wgNewsFeedURLPattern);
+ }
+ else {
+ $q = 'feed=' . urlencode( $format );
+ #if ($feedId) $q .= '&feed=' . urlencode( $feedId );
+
+ $url = $title->getFullUrl($q);
+ }
+
+ return $url;
+ }
+
+ static function renderFeedLink( $text, $argv, &$parser ) {
+ $t = @$argv['feed'];
+ if ($t) $t = $parser->replaceVariables($t, array());
+
+ $title = $t === NULL ? NULL : Title::newFromText($t);
+ if (!$title) $title = $GLOBALS['wgTitle'];
+
+ #$id = @$argv['id'];
+ $format = @$argv['format'];
+ if (!$format) $format = 'rss';
+ else $format = strtolower(trim($format));
+
+ $icon = @$argv['icon'];
+ $iconright = false;
+ if (preg_match('/^(.+)\|(\w+)$/', $icon, $m)) {
+ $icon = $m[1];
+ $iconright = ( strtolower(trim($m[2])) === 'right' );
+ }
+
+ $ticon = $icon ? Title::newFromText($icon, NS_IMAGE) : NULL;
+ $image = $ticon ? new Image( $ticon ) : NULL;
+ $thumb = $image ? $image->getThumbnail(80, 16) : NULL;
+ if ($image && !$thumb) $thumb = $image;
+ $iconurl = $thumb ? $thumb->getUrl() : NULL;
+
+ $url = NewsRenderer::getFeedURL($title, $format);
+
+ $ttl = @$argv['title'];
+ if ($ttl) $ttl = $parser->replaceVariables($ttl, array());
+
+ $s = '';
+ if ($text) {
+ $s .= $parser->recursiveTagParse($text);
+ if (!$ttl) $ttl = $text . ' (' . $format . ')';
+ }
+ else {
+ if (!$ttl) $ttl = $format;
+ }
+
+ if ($iconurl) {
+ $ic = '<img border="0" src="'.htmlspecialchars($iconurl).'" alt="'.htmlspecialchars($ttl).'" title="'.htmlspecialchars($ttl).'"/>';
+ if ($s === '') $s = $ic;
+ else if ($iconright) $s = "$s $ic";
+ else $s = "$ic $s";
+ }
+
+ $html = '<a href="'.htmlspecialchars($url).'" title="'.htmlspecialchars($ttl).'">'.$s.'</a>';
+ return $html;
+ }
+
+ static function getLastChangeTime( ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ list( $trecentchanges ) = $dbr->tableNamesN( 'recentchanges' );
+
+ $sql = 'select max(rc_timestamp) from ' . $trecentchanges;
+ $res = $dbr->query( $sql, 'NewsRenderer::getLastChangeTime' );
+ if (!$res) return false;
+
+ $row = $dbr->fetchRow($res);
+ if (!$row) return false;
+
+ return $row[0];
+ }
+
+ static function sanitizeWikiText( $text ) {
+ global $wgParser;
+
+ $elements = array_keys( $wgParser->mTagHooks );
+ $uniq_prefix = "\x07NR-UNIQ";
+
+ $matches = array();
+ $text = Parser::extractTagsAndParams( $elements, $text, $matches, $uniq_prefix );
+
+ foreach( $matches as $marker => $data ) {
+ list( $element, $content, $params, $tag ) = $data;
+ $tagName = strtolower( $element );
+
+ $output = '';
+ if ($tagName == '!--') $output = $tag; //keep comments for now, may be stripped later
+
+ #print "* $marker => " . htmlspecialchars($output) . "<br/>\n";
+ $text = str_replace($marker, $output, $text);
+ }
+
+ return $text;
+ }
+
+ private static function cutHead( $text, $separators, $suffix ) {
+ $i = NEWS_HEAD_LENGTH - 1;
+ while ($i > NEWS_HEAD_LENGTH - NEWS_HEAD_SCAN) {
+ $ch = substr($text, $i, 1);
+ if (in_array($ch, $separators)) {
+ $text = substr($text, 0, $i);
+ return trim($text) . $suffix;
+ }
+
+ $i -= 1;
+ }
+
+ return false;
+ }
+
+ static function extractHead( $text, $title = NULL ) {
+ $text = trim($text);
+
+ if ( strlen($text) < NEWS_HEAD_LENGTH ) return $text;
+
+ $suffix = "\n" . '... ([[' . $title->getPrefixedText() . ']]...)';
+
+ $t = preg_replace('/^(.*?)<!--\s*summary\s+end\s*-->.*$/si', '\1', $text);
+ if ($t != $text) return trim($t);
+
+ if ( $t = NewsRenderer::cutHead($text, array("\r", "\n"), $suffix) ) return $t;
+ if ( $t = NewsRenderer::cutHead($text, array("."), $suffix) ) return $t;
+ if ( $t = NewsRenderer::cutHead($text, array(" ", "\t"), $suffix) ) return $t;
+
+ $text = substr($text, 0, 512) . $suffix;
+ return $text;
+ }
}
?>
\ No newline at end of file
Index: trunk/extensions/News/README
===================================================================
--- trunk/extensions/News/README (revision 21282)
+++ trunk/extensions/News/README (revision 21283)
@@ -5,8 +5,8 @@
GNU Free Documentation License (GFDL)
--------------------------------------------------------------------------
-The News extension provides a custom tag, <news>, that allows the inclusion of
-an excerpt from the Special:Recentchanges page to be shown on any wiki page.
+The News extension allows a custom excerpt from Special:Recentchanges to be
+included on a wiki page, or to be published as an RSS or Atom feed.
It supports several types of filtering as well as full custom formating of
entries, using template syntax.
@@ -18,7 +18,7 @@
Note that the functionality of this extension overlaps with the DynamicPageList
and DynamicPageList2 extensions - however, this extension has a different focus.
-== INSTALLING ==
+== Installing ==
Copy the News directory into the extensions folder of your
MediaWiki installation. Then add the following line to your
@@ -26,8 +26,20 @@
require_once( "$IP/extensions/News/News.php" );
-== USAGE ==
+== Usage ==
+The News extension prvodes provides three custom tags:
+* <news>: this includes a list of recent changes on the wiki page
+* <newsfeed>: this defines a news feed of recent changes; on the wiki page,
+ a preview is rendered, similar to the output of the <news> tag; the wiki
+ page then also supports the newsfeed action, which returns the feed in
+ RSS or Atom format.
+* <newsfeedlink>: this creates a link to a news feed defined using a
+ <newsfeed> tag. This is convenient for creating prominent links to the
+ news feeds.
+
+=== Filtering and formatting ===
+
To get the last 10 changes to your wiki on any wiki page, use the
following:
@@ -47,9 +59,13 @@
For a full list of options and template parameters, see below.
-=== OPTIONS ===
+The <newsfeed> tag supports the same optiosn for filtering and
+formatting as the <news> tag. For information on how to access a feed defined
+using <newsfeed>, see the section "Accessing Feeds" below.
+
+=== Options ===
The following options (tag attributes) can be used to controll the output of the
-<news> tags:
+<news> and <newsfeed> tags:
* unique show only the most recent change to each page
@@ -83,7 +99,13 @@
"false"). If given, the edit shown may not refer to the current
revision.
-=== PARAMETERS ===
+* prefix wikitext to be inserted before the wikitext generated from the
+ template text is parsed. Can be used to make tables from news.
+
+* postfix wikitext to be inserted after the wikitext generated from the
+ template text is parsed. Can be used to make tables from news.
+
+=== Parameters ===
When giving a template text between the <news> tags, the following
template-parameters are available (use them as {{{xxx}}}):
@@ -130,3 +152,55 @@
* new_len page length after the edit
+* content the full content of the page
+
+* head the page's content up to about 2KB of text, with smart cut-off.
+
+=== Accessing Feeds ===
+
+If the page Foo defines a feed using a <newsfeed> tag, that feed can be
+referenced by using feed=rss or feed=atom in the url respectively.
+
+So, if the URL path for page Foo is /wiki/Foo, you can use
+ /wiki/Foo?feed=rss
+to get an RSS feed for that page. If the URL is /w/index.php?title=Foo,
+you would use
+ /w/index.php?title=Foo&feed=rss
+
+You can conveniently create links to feeds using the <newsfeedlink> tag:
+For example,
+ <newsfeedlink feed="Foo" format="rss">My Foo Feed</newsfeedlink>
+would generate a link to the news feed defined on page Foo using a
+<newsfeed> tag.
+
+You can also specify an icon to use in the link:
+ <newsfeedlink feed="Foo" format="rss" icon="rss.png">My Foo Feed</newsfeedlink>
+would generate a link that has the image "rss.png" before the link text
+(the icon option refers to the name of an image uploaded to the wiki).
+ <newsfeedlink feed="Foo" format="rss" icon="rss.png|right">My Foo Feed</newsfeedlink>
+would generate a link that has the image to the right of the link text; and
+ <newsfeedlink feed="Foo" format="rss" icon="rss.png" title="RSS feed"/>
+generates a link that only shows the given icon. The title attribute specifies
+the tooltip to show when the mouse hovers over the link.
+
+Note that the link text may contain full wiki text, and the title-attribute may
+contain variables like {{PAGENAME}}.
+
+== Configuration ==
+Configuration settings to define in LocalSettings.php
+
+* $wgNewsFeedURLPattern: this defines the pattern used by the <newsfeedlink>
+tag to generate feed URLs. In the pattern, $1 will be replaced by the page title,
+and $2 will be replaced by the requested feed format.
+If you are using pretty URLs with $wgArticlePath set to $wgScript/$1 or /wiki/$1,
+etc, you can use the following for nicer feed URLs:
+ $wgNewsFeedURLPattern = $wgArticlePath . '?feed=$2';
+(note that $wgArticlePath already contains $1 withe the meaning "page title")
+If you want to use rewrite rules for canonical feed URLs, like /feed/Foo.rss,
+set
+ $wgNewsFeedURLPattern = '/feed/$1.$2';
+
+* $wgNewsFeedUserPattern: this defines the pattern used to generate author
+names in feed items. In the pattern, $1 is replaced by the user name. To
+e.g. generate email-addresses at your site as author names, use
+ $wgNewsFeedUserPattern = '$1@' . $wgServerName;