Extension:Blurb

From MediaWiki.org
Jump to: navigation, search
MediaWiki extensions manual - list
Crystal Clear action run.png
Blurb

Release status: beta

BlurbExtension.png
Implementation Tag
Description Allow a custom excerpt from a random page to be included on a wiki page.
Author(s) Duesentrieb (mainly) and Leucosticte
MediaWiki 1.9-1.11, 1.13 and later, not 1.12. Tested on 1.19, 1.20. Does not work with later version of MediaWiki.
Database changes no
License GPL
Download No link
Example http://nomicwiki.com/
Parameters
  • $wgNewsFeedURLPattern
  • $wgNewsFeedUserPattern
  • $wgBlurbHeadLength
  • $wgBlurbHeadScan
  • $wgBlurbMinimumLength
  • $wgBlurbSubstr
  • $wgBlurbExclude
Tags
<blurbfeed/>
Hooks used
ArticleFromTitle

SkinTemplateOutputPageBeforeExec
ParserFirstCallInit

Translate the Blurb extension if possible

Check usage and version matrix; code metrics

The Blurb extension allows a custom excerpt from Special:RandomPage to be included on a wiki page. This can be used to implement a rotating featured article, for example.

Installing[edit | edit source]

Copy the Blurb directory into the extensions folder of your MediaWiki installation. Then add the following line to your LocalSettings.php file (near the end):

require_once( "$IP/extensions/Blurb/Blurb.php" );

Usage[edit | edit source]

<blurbfeed/>
this defines a blurb of a random page; on the wiki page, a preview is rendered.

Configuration[edit | edit source]

Configuration settings to define in LocalSettings.php:

$wgNewsFeedURLPattern[edit | edit source]

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[edit | edit source]

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;

$wgBlurbHeadLength[edit | edit source]

Defaults to 1024 * 4 characters.

$wgBlurbHeadScan[edit | edit source]

Defaults to 512 characters.

$wgBlurbMinimumLength[edit | edit source]

Minimum length that a page must be to appear in the blurb. Defaults to 512 characters.

$wgBlurbSubstr[edit | edit source]

Defaults to 1024 characters.

$wgBlurbExclude[edit | edit source]

An array of pages to exclude from your blurbs. By default, the main page is excluded.

Code[edit | edit source]

Blurb.php[edit | edit source]

<?php
/**
 * Blurb extension - shows recent changes on a wiki page.
 *
 * @file
 * @ingroup Extensions
 * @author Daniel Kinzler, brightbyte.de
 * @author Leucosticte
 * @copyright © 2007 Daniel Kinzler
 * @licence GNU General Public Licence 2.0 or later
 */
 
 
if( !defined( 'MEDIAWIKI' ) ) {
	echo( "This file is an extension to the MediaWiki software and cannot be used standalone.\n" );
	die( 1 );
}
 
$wgExtensionCredits['other'][] = array( 
	'path' => __FILE__,
	'name' => 'Blurb', 
	'author' => 'Daniel Kinzler, brightbyte.de; modified by [http://mediawiki.org/User:Leucosticte Leucosticte]', 
	'url' => 'http://mediawiki.org/wiki/Extension:Blurb',
	'descriptionmsg' => 'blurbextension-desc',
);
 
$dir = dirname(__FILE__) . '/';
$wgExtensionMessagesFiles['BlurbExtension'] = $dir . 'Blurb.i18n.php';
 
$wgBlurbFeedURLPattern = false; // pattern for feed-URLs; useful when using rewrites for canonical feed URLs
$wgBlurbFeedUserPattern = false; // pattern to use for the author-field in feed items.
 
$wgAutoloadClasses['BlurbRenderer'] = dirname( __FILE__ ) . '/BlurbRenderer.php';
$wgAutoloadClasses['BlurbFeedPage'] = dirname( __FILE__ ) . '/BlurbRenderer.php';
$wgHooks['ArticleFromTitle'][] = 'wfBlurbArticleFromTitle';
$wgHooks['SkinTemplateOutputPageBeforeExec'][] = 'wfBlurbSkinTemplateOutputPageBeforeExec';
$wgHooks['ParserFirstCallInit'][] = 'wfBlurbSetHooks';
 
$wgBlurbHeadLength = 1024 * 4;
$wgBlurbHeadScan = 512;
$wgBlurbMinimumLength = 512;
$wgBlurbSubstr = 1024;
$wgBlurbExclude = array ( 'Main Page' );
 
//FIXME: find a way to override the feed URLs generated by OutputPage::getHeadLinks
 
function wfBlurbSetHooks( $parser ) {
	$parser->setHook( 'blurb', 'wfBlurbTag' );
	$parser->setHook( 'blurbfeed', 'wfBlurbFeedTag' );
	$parser->setHook( 'blurbfeedlink', 'wfBlurbFeedLinkTag' );
 
	return true;
}
 
function wfBlurbTag( $templatetext, $argv, $parser ) {
    global $wgTitle;
 
    $parser->disableCache(); //TODO: use smart cache & purge...?
    $renderer = new BlurbRenderer($wgTitle, $templatetext, $argv, $parser);
 
    return $renderer->renderBlurb();
}
 
function wfBlurbFeedTag( $templatetext, $argv, $parser ) {
    global $wgTitle, $wgOut;
 
    $parser->disableCache(); //TODO: use smart cache & purge...?
    $wgOut->setSyndicated( true );
 
    $silent = @$argv['silent'];
    if ( $silent === 'false' || $silent === 'no' || $silent === '0' )
        $silent = false;
 
    if ( $silent ) return "";
 
    $renderer = new BlurbRenderer($wgTitle, $templatetext, $argv, $parser);
    $html = $renderer->renderFeedPreview();
    return $html;
}
 
function wfBlurbFeedLinkTag( $linktext, $argv, $parser ) {
    return BlurbRenderer::renderFeedLink($linktext, $argv, $parser);
}
 
function wfBlurbArticleFromTitle( $title, &$article ) {
    global $wgRequest, $wgFeedClasses, $wgUser, $wgOut;
    $fname = 'extension/Blurb: wfBlurbArticleFromTitle';
 
    $ns = $title->getNamespace();
    if ($ns < 0 || $ns == NS_SPECIAL || $ns == NS_MEDIAWIKI) return true;
 
    $format = $wgRequest->getVal( 'feed' );
    if (!$format) return true; 
 
    $format = strtolower( trim($format) );
 
    $action = strtolower( trim( $wgRequest->getVal( 'action', 'view' ) ) );
    if ($action != 'view' && $action != 'purge') return true;
 
    if ( !isset($wgFeedClasses[$format] ) ) {
	wfDebug( "$fname: unknown feed format: $format \n" );
        wfHttpError(400, "Bad Request", "unknown feed format: " . $format); //TODO: better code & text
        return false;
    }
 
    if (!$title->exists()) {
	wfDebug( "$fname: feed page not found: " . $title->getPrefixedDBKey() . "\n" );
        wfHttpError(404, "Not Found", "feed page not found: " . $title->getPrefixedText()); //TODO: better text
        return false;
    }
 
    wfDebug( "$fname: handling feed request for " . $title->getPrefixedDBKey() . "\n" );
 
    $article = new BlurbFeedPage( $title, $format );
    return false;
}
 
function wfBlurbSkinTemplateOutputPageBeforeExec( $skin, $tpl ) {
    $feeds = $tpl->data['feeds'];
    if (!$feeds) return true;
 
    $title = $skin->getTitle(); //hack...
 
    foreach ($feeds as $format => $e) {
        $e['href'] = BlurbRenderer::getFeedURL( $title, $format );
        $feeds[$format] = $e;
    }
 
    $tpl->setRef( 'feeds', $feeds );
    return true;
}

BlurbRenderer.php[edit | edit source]

<?php
/**
 * Blurb renderer for Blurb extension.
 *
 * @file
 * @ingroup Extensions
 * @author Daniel Kinzler, brightbyte.de
 * @author Leucosticte
 * @copyright © 2007 Daniel Kinzler
 * @licence GNU General Public Licence 2.0 or later
 */
 
if( !defined( 'MEDIAWIKI' ) ) {
	echo( "Not a valid entry point.\n" );
	die( 1 );
}
 
class BlurbRenderer {
	var $parser;
	var $skin;
 
	var $title;
 
	var $prefix;
	var $postfix;
 
	var $usetemplate;
	var $templatetext;
	var $templateparser;
	var $templateoptions;
 
	var $changelist;
 
	var $namespaces;
	var $categories;
	var $types;
 
	var $nominor;
	var $noanon;
	var $nobot;
	var $notalk;
 
	var $onlynew;
	var $onlypatrolled;
 
	var $publication; //"publication" mode, as opposed to the default "updates" mode
	var $pubtrigger; //word to use in summaries to trigger publication
	var $permalinks; //wether to force permalinks in feeds, even in publication mode
 
	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', 'blurbfeed');
		$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 != 'blurbfeed') continue;
			#if (!is_null($id) && (!isset($params['id']) || $params['id'] != $id)) continue;

			return new BlurbRenderer( $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;
 
		$this->templatetext = $templatetext;
 
		if ( !is_null( $this->templatetext ) ) {
			$this->templatetext = trim( $this->templatetext );
			if ( $this->templatetext == '' ) $this->templatetext = null;
		}
 
		$this->usetemplate = !is_null( $this->templatetext );
 
		$this->templateparser = null;
		$this->templateoptions = null;
 
		#$template = @$argv['template'];

		#if ( $this->usetemplate ) {
			#print "<pre>$templatetitle</pre>";

			$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->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>";

			#$templateoptions->setRemoveComments( true );
			#$templateoptions->setMaxIncludeSize( self::MAX_INCLUDE_SIZE );
		#}

		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;
		elseif ( $this->limit > 100 ) $this->limit = 100;
 
		$this->unique = @$argv['unique'];
		if ( $this->unique === 'false' || $this->unique === 'no' || $this->unique === '0' )
			$this->unique = false;
 
		$this->namespaces = @$argv['namespaces'];
		if ( !is_null( $this->namespaces ) ) {
			$this->namespaces = preg_split('!\s*(\|\s*)+!', trim( $this->namespaces ) );
 
			foreach ($this->namespaces as $i => $ns) {
				$ns = $wgContLang->lc($ns);
 
				if ( $ns === '-' || $ns === '0' || $ns === 'main' || $ns === 'article' ) {
					$this->namespaces[$i] = 0;
				} else {
					$this->namespaces[$i] = MWNamespace::getCanonicalIndex( $ns );
					if ( $this->namespaces[$i] === false || $this->namespaces[$i] === null )
						$this->namespaces[$i] = $wgContLang->getNsIndex( $ns );
				}
 
				if ( $this->namespaces[$i] === false || $this->namespaces[$i] === null )
					unset( $this->namespaces[$i] );
			}
		}
 
		$this->categories = @$argv['categories'];
		if ( !is_null( $this->categories ) ) {
			$this->categories = preg_split('!\s*(\|\s*)+!', trim( $this->categories ) );
 
			foreach ($this->categories as $i => $n) {
				$t = Title::makeTitleSafe(NS_CATEGORY, $n);
				$n = $t->getDBkey();
				$this->categories[$i] = $n;
			}
		}
 
		$this->pubtrigger = @$argv['trigger'];
		if ( $this->pubtrigger ) $this->publication = true;
		else  $this->publication = false;
 
		$this->permalinks = @$argv['permalinks'];
		if ( $this->permalinks === 'false' || $this->permalinks === 'no' || $this->permalinks === '0' )
			$this->permalinks = false;
 
		$this->nominor = @$argv['nominor'];
		if ( $this->nominor === 'false' || $this->nominor === 'no' || $this->nominor === '0' )
			$this->nominor = false;
 
		$this->nobot = @$argv['nobot'];
		if ( $this->nobot === 'false' || $this->nobot === 'no' || $this->nobot === '0' )
			$this->nobot = false;
 
		$this->noanon = @$argv['noanon'];
		if ( $this->noanon === 'false' || $this->noanon === 'no' || $this->noanon === '0' )
			$this->noanon = false;
 
		$this->notalk = @$argv['notalk'];
		if ( $this->notalk === 'false' || $this->notalk === 'no' || $this->notalk === '0' )
			$this->notalk = false;
 
		$this->onlypatrolled = @$argv['onlypatrolled'];
		if ( $this->onlypatrolled === 'false' || $this->onlypatrolled === 'no' || $this->onlypatrolled === '0' )
			$this->onlypatrolled = false;
 
		$this->onlynew = @$argv['onlynew'];
		if ( $this->onlynew === 'false' || $this->onlynew === 'no' || $this->onlynew === '0' )
			$this->onlynew = false;
 
		$this->types = array( RC_EDIT, RC_NEW );
 
		/* this doesn't work right
		if ( $unique ) {
			$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( ) {
            global $wgBlurbMinimumLength, $wgBlurbExclude;
            $verified = false;
            while ( $verified == false ) {
                $blurbRandomPage = new RandomPageWithMinimumLength ( $wgBlurbMinimumLength );
                $blurbRandomTitle = $blurbRandomPage->getRandomTitle();
                if ( !in_array( $blurbRandomTitle->getPrefixedText(), $wgBlurbExclude ) ) {
                    break;
                }
            }
            $thisRevision = Revision::newFromTitle( $blurbRandomTitle );
            $previousRevision = $thisRevision->getPrevious();
            $row = array (
                'rc_user_text' => $thisRevision->getUserText(),
                'rc_user' => $thisRevision->getUser(),
                'rc_type' => 0,
                'rc_title' => $blurbRandomTitle->getDBkey(),
                'rc_timestamp' => $thisRevision->getTimestamp(),
                'rc_this_oldid' => $thisRevision->getId(),
                'rc_patrolled' => 1,
                'rc_old_len' => $previousRevision ? $previousRevision->getSize() : 0,
                'rc_new_len' => $thisRevision->getSize(),
                'rc_new' => $blurbRandomTitle->isNewPage() ? 1 : 0,
                'rc_namespace' => $blurbRandomTitle->getNamespace(),
                'rc_minor' => $thisRevision->isMinor() ? 1 : 0,
                'rc_logtype' => NULL,
                'rc_logid' => 0,
                'rc_log_action' => NULL,
                'rc_last_oldid' => $previousRevision ? $previousRevision->getId() : 0,
                'rc_ip' => '0.0.0.0',
                'rc_id' => 0,
                'rc_cur_id' => $thisRevision->getPage(),
                'rc_comment' => $thisRevision->getComment(),
                'rc_bot' => 0,
            );
            $res[] = $row;
            /*
		list( $trecentchanges, $tpage, $tcategorylinks ) = $dbr->tableNamesN( 'recentchanges', 'page', 'categorylinks' );
 
		$where = array();
		$group = array();
		$select = "$trecentchanges.*";
 
		$sql = "SELECT $select FROM $trecentchanges ";
 
		if ( $this->categories ) {
			$sql .= " JOIN $tpage ON page_namespace = rc_namespace AND page_title = rc_title ";
			$sql .= " JOIN $tcategorylinks ON cl_from = page_id ";
 
			$where[] = 'cl_to IN ( ' . $dbr->makeList( $this->categories ) . ' )';
			$group[] = 'rc_id';
		}
 
		if ( $this->nominor )  $where[] = 'rc_minor = 0';
		if ( $this->nobot )  $where[] = 'rc_bot = 0';
		if ( $this->noanon )  $where[] = 'rc_user > 0';
		if ( $this->onlypatrolled )  $where[] = 'rc_patrolled = 1';
		if ( $this->onlynew )  $where[] = 'rc_new = 1';
		if ( $this->pubtrigger )  $where[] = 'rc_comment LIKE ' . $dbr->addQuotes( '%' . $this->pubtrigger . '%' );
 
		if ( $this->namespaces )  $where[] = 'rc_namespace IN ( ' . $dbr->makeList( $this->namespaces ) . ' )';
		else {
			if ( $this->notalk )  $where[] = 'MOD(rc_namespace, 2) = 0';
			$where[] = 'rc_namespace >= 0'; #ignore virtual namespaces (logs, mostly)
		}
 
 
		$where[] = 'rc_type IN ( ' . $dbr->makeList( $this->types ) . ' )';
 
		if ( $where ) $sql .= ' WHERE ( ' . implode( ' ) AND ( ', $where ) . ' )';
		if ( $group ) $sql .= ' GROUP BY ' . implode( ' AND ', $group );
 
		$sql .= ' ORDER BY rc_timestamp DESC ';
 
		$sql = $dbr->limitResult( $sql, $limit, $offset );
 
		$res = $dbr->query( $sql, 'BlurbRenderer::query' );
                */
		return $res;
	}
 
	function fetchBlurb( ) {
		#$dbr = wfGetDB( DB_SLAVE );
		$blurb = array();
 
		$remaining = $this->limit;
		$offset = 0;
		$ignore = array(); #collect stuff we already have, when in unique mode
                $res = $this->query( );
		/*
                while ( $remaining > 0 ) { #chunk loop for programmatic filter
			$chunk = $this->unique ? $remaining * 2 : $remaining;
			$res = $this->query( $dbr, $chunk, $offset );
			$offset += $chunk;
 
			$has = false;
			while ( ( $remaining > 0 ) && ( $row = $dbr->fetchObject($res) ) ) {
				$has = true;
 
				if ( $this->unique && $row->rc_namespace >= 0 ) {
					$k = $row->rc_namespace . ':' . $row->rc_title;
					if ( isset( $ignore[$k] ) ) continue;
					$ignore[$k] = true;
				}
 
				$blurb[] = $row;
				$remaining -= 1;
			}
 
			#$dbr->freeResult( $res );
 
			if ( !$has ) break; #empty result set, stop trying
		}
                */
                return $res;
		#return $blurb;
	}
 
	function renderBlurb( ) {
		global $wgTitle;
 
		$blurb = $this->fetchBlurb();
 
		$text = '';
 
		if ( $this->usetemplate ) {
			$text .= $this->prefix;
		}
		else {
			$text .= $this->changelist->beginRecentChangesList();
		}
 
		foreach ($blurb 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 );
			$text .= $this->postfix;
			$html = $this->templateparser->recursiveTagParse( $text );
		}
		else { #it's already html
			$text .= $this->changelist->endRecentChangesList();
			$html = $text;
		}
 
		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 );
 
		$blurb = $this->fetchBlurb();
 
		ob_start();
		$feed->outHeader();
		foreach ($blurb 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 renderFeedPreview( ) {
		$blurb = $this->fetchBlurb();
 
		$html = '';
		$html .= '<div class="hfeed"> <!-- using hatom microformat, see http://microformats.org/wiki/hatom -->';
		foreach ($blurb as $row) {
			$t = $this->renderRow( $row, true );
			$item = $this->makeFeedItem( $row, $t, false );
			$t = $this->renderFeedItem( $item );
			$html .= $t;
		}
		$html .= '</div>';
 
		return $html;
	}
 
	function renderFeedItem( $item ) {
		global $wgContLang, $wgUser;
		$sk = $wgUser->getSkin();
 
		$html = '';
		$html .= '<div class="blurbfeed-item hentry">';
		$html .= '<div class="blurbfeed-item-head">';
 
		$html .= '<h1><a href="'.$item->getUrl().'" class="entry-title" rel="bookmark">' . $item->getTitle() . '</a></h1>';
 
		$html .= '<p><small>';
		$html .= '<span class="author">' . $item->getAuthor() . '</span>';
		$html .= ', ';
		$html .= '<span class="published">' . $wgContLang->timeanddate( $item->getDate() ) . '</span>';
		$html .= '</small></p>';
 
		$html .= '</div>';
 
		$html .= '<div class="blurbfeed-item-content entry-content">';
		$html .= $item->raw_text;
		$html .= '</div>';
		$html .= '<p><small>';
		if ( $item->getComments() ) {
			$html .= '(';
			$html .= '<a href="'.htmlspecialchars( $item->raw_comment ).'"/>'.htmlspecialchars($item->title_object->getTalkPage()->getPrefixedText()).'</a>';
			$html .= ')';
		}
		$html .= '</small></p>';
		$html .= '</div>';
		return $html;
	}
 
	function makeFeedItem( $row, $text, $standalone ) {
		global $wgBlurbFeedUserPattern;
 
		$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 ( $wgBlurbFeedUserPattern ) {
			$user = str_replace('$1', $row->rc_user_text, $wgBlurbFeedUserPattern);
		}
		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 :(
 
		if ( $this->publication || $row['rc_new'] ) {
			$name = $title->getPrefixedText();
		}
		else {
			$name = $title->getPrefixedText();
			$permaq = "oldid=" . $row['rc_this_oldid'];
		}
 
		/*if (!$this->publication || $this->permalinks) {
			$url = $row['rc_this_oldid'] ? $title->getFullURL( $permaq ) : $title->getFullURL();
		}
		else {*/
			$url = $title->getFullURL();
		#}

		$item = new FeedItem( $name,
					$text,
					$url,
					$row['rc_timestamp'],
					$user,
					$title->getTalkPage()->getFullURL() );
 
		//XXX: ugly hack - things used by preview
		$item->raw_text = $text; //needed because FeedItem holds text html-encoded internally. wtf
		$item->raw_comment = $title->getTalkPage()->getFullURL(); //needed because FeedItem holds text html-encoded internally. wtf
		$item->raw_title = $name; //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 = new RecentChange;
                $change->setAttribs ( $row );
		$change->counter = 0; //hack
 
		$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
			$html = $this->changelist->recentChangesLine( $change );
 
			return $html;
		}
		else {
			$params = array();
 
			$params['namespace'] = $row['rc_namespace'];
			$params['title'] = $row['rc_title'];
 
			$title = $change->getTitle();
			$params['pagename'] = $title->getPrefixedText();
 
			$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' : ''; #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'];
			$params['user'] = $row['rc_user_text'];
 
			$params['rawtime'] = $row['rc_timestamp'];
			$params['time'] = $wgLang->time( $row['rc_timestamp'], true, true );
			$params['date'] = $wgLang->date( $row['rc_timestamp'], true, true );
			$params['timeanddate'] = $wgLang->timeanddate( $row['rc_timestamp'], true, true );
 
			$params['old_len'] = $row['rc_old_len'];
			$params['new_len'] = $row['rc_new_len'];
 
			$params['old_rev'] = $row['rc_last_oldid'];
			$params['new_rev'] = $row['rc_this_oldid'];
 
			$diffq = $change->diffLinkTrail( false );
			$params['diff'] = $diffq ? $title->getFullURL( $diffq ) : '';
 
			$permaq = "oldid=" . $row['rc_this_oldid'];
			$params['permalink'] = $permaq ? $title->getFullURL( $permaq ) : '';
 
			$params['comment'] = str_replace( array( '{{', '}}', '|', '\'' ), array( '&#123;&#123;', '&#125;&#125;', '&#124;', '$#39;' ), wfEscapeWikiText( $row['rc_comment'] ) );
 
			if ( stripos($templatetext, '{{{content}}}')!==false || stripos($templatetext, '{{{head}}}')!==false ) {
				$article = new Article( $title, $row['rc_this_oldid'] );
				$t = $article->getContent();
 
				//TODO: expand variables & templates first, so cut-off applies to effective content,
				//      and extension tags from templates are stripped properly
				//      this doesn't work though: $t = $this->templateparser->preprocess( $t, $this->title, new ParserOptions() );
				//TODO: avoid magic categories, interwiki-links, etc
				$params['content'] = BlurbRenderer::sanitizeWikiText( $t, $this->templateparser );
 
				if ( stripos($templatetext, '{{{head}}}')!==false ) {
					$params['head'] = BlurbRenderer::extractHead( $params['content'], $title );
				}
			}
 
			$text = BlurbRenderer::replaceVariables( $this->templateparser, $templatetext, $params, $this->title );
			return $text;
		}
	}
 
	static function replaceVariables($parser, $text, $params = null, $title = null) {
		if ( $params === null ) $params = array();
		$text = $parser->replaceVariables( $text, $params );
		return $text;
	}
 
	/*
	function renderFeedMetaLink( $format ) {
		$format = strtolower(trim($format));
 
		$name = $format;
		if ($name == 'rss') $name = 'RSS 2.0';
		elseif ($name == 'atom') $name = 'Atom 1.0';
 
		$mime = "application/$format+xml"; //hack
		$url = BlurbRenderer::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 $wgBlurbFeedURLPattern;
 
		if ( $wgBlurbFeedURLPattern ) {
			$params = array(
				'$1' => urlencode( $title->getPrefixedDBKey() ),
				'$2' => urlencode( $format ),
				#'$3' => urlencode( $feedId ),
			);
 
			$url = str_replace(array_keys($params), array_values($params), $wgBlurbFeedURLPattern);
		}
		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 = BlurbRenderer::replaceVariables( $parser, $t );
 
		$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;
		if ( $ticon ) {
			$image = wfFindFile( $ticon );
			if ( !$image->exists() ) {
				$image = false;
			}
		} else {
			$image = false;
		}
 
		$thumb = $image ? $image->transform( array( 'width' => 80, 'height' => 16 ), 0 ) : null;
		if ($image && !$thumb) $thumb = $image;
		$iconurl = $thumb ? $thumb->getUrl() : null;
 
		$url = BlurbRenderer::getFeedURL($title, $format);
 
		$ttl = @$argv['title'];
		if ($ttl) $ttl = BlurbRenderer::replaceVariables( $parser, $ttl );
 
		$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;
			elseif ($iconright) $s = "$s&#160;$ic";
			else $s = "$ic&#160;$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, 'BlurbRenderer::getLastChangeTime' );
		if (!$res) return false;
 
		$row = $dbr->fetchRow($res);
		if (!$row) return false;
 
		return $row[0];
	}
 
	static function sanitizeWikiText( $text, $parser = null ) {
		if ( !$parser ) $parser = $GLOBALS['wgParser'];
 
		$elements = array_keys( $parser->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 ) {
		global $wgBlurbHeadLength, $wgBlurbHeadScan;
                $i = $wgBlurbHeadLength - 1;
		while ($i > $wgBlurbHeadLength - $wgBlurbHeadScan ) {
			$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 ) {
                global $wgBlurbHeadLength, $wgBlurbSubstr;
		$text = trim($text);
 
		if ( strlen($text) < $wgBlurbHeadLength ) return $text;
 
		$suffix = ' &#091;...[[' . $title->getPrefixedText() . ']]...&#093;';
 
		$t = preg_replace('/^(.*?)<!--\s*summary\s+end\s*-->.*$/si', '\1', $text);
		if ($t != $text) return trim($t) . $suffix;
 
		if ( $t = BlurbRenderer::cutHead($text, array("\r", "\n"), $suffix) ) return $t;
		if ( $t = BlurbRenderer::cutHead($text, array("."), $suffix) ) return $t;
		if ( $t = BlurbRenderer::cutHead($text, array(" ", "\t"), $suffix) ) return $t;
 
		$text = substr($text, 0, $wgBlurbSubstr ) . $suffix;
		return $text;
	}
 
}
 
class BlurbFeedPage extends Article {
	var $mFeedFormat;
 
	function __construct($title, $format) {
		Article::__construct( $title );
		$this->mFeedFormat = $format;
	}
 
	function getCacheKey( ) {
		//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 "@blurbfeed:" . urlencode($this->mTitle->getPrefixedDBKey()) . '|' . urlencode($this->mFeedFormat);
	}
 
	function view( $usecache = true ) {
		global $wgUser, $wgOut;
 
		$fname = 'BlurbFeedPage::view';
		wfDebug("$fname: start\n");
 
		$note = '';
                /*
		$ims = @$_SERVER['HTTP_IF_MODIFIED_SINCE'];
 
		//TODO: cache control!
 
		if ( $ims && $usecache ) {
			$lastchange = wfTimestamp(TS_UNIX, BlurbRenderer::getLastChangeTime());
 
			wfDebug( "$fname: checking cache-ok:  IMS $ims vs. changed $lastchange \n" );
			if ( $wgOut->checkLastModified( $lastchange ) ) {
				wfDebug( "$fname: HTTP cache ok, 304 header sent \n" );
				return; // done, 304 header sent.
			}
		}
 
		//NOTE: do caching for anon users only, because of user-specific
		//      rendering of textual content
		if ($wgUser->isAnon() && $usecache) {
			$cachekey = $this->getCacheKey();
			$ocache = wfGetParserCacheStorage();
			$e = $ocache ? $ocache->get( $cachekey ) : null;
			$note .= ' anon;';
			$debug = $e ? "got cached" : "no cached";
			wfDebug( "$fname: ($debug) \n" );
		}
		else {
			if (!$usecache) {
				wfDebug( "$fname: purge, ignoring cache \n" );
				$note .= ' purged;';
			}
			else {
				wfDebug( "$fname: logged in, ignoring cache \n" );
				$note .= ' user;';
			}
 
			$cachekey = null;
			$ocache = null;
			$e = null;
			$note .= ' user;';
		}
 
		$wgOut->disable();
 
		if ( $e ) {
			if (!isset($lastchange)) $lastchange = wfTimestamp(TS_UNIX, BlurbRenderer::getLastChangeTime());
			$last = wfTimestamp(TS_UNIX, $lastchange);
 
			if ($last < $e['timestamp']) {
				wfDebug( "$fname: outputting cached copy ($cacheKey): $last < " . $e['timestamp'] . " \n" );
				header('Content-Type: application/' . $this->mFeedFormat . '+xml; charset=UTF-8');
 
				print $e['xml'];
				print "\n<!-- cached: $note -->\n";
 
				return; //done
			}
			else {
				wfDebug( "$fname: found stale cached copy ($cacheKey): $last <= " . $e['timestamp'] . " \n" );
				$note .= " stale: $last >= {$e['timestamp']};";
			}
		}
                */
		//TODO: fetch actual blurb data and check the newest item. re-apply cache checks.
		//      this would still save the cost of rendering if the data didn't change
		global $wgParser; //evil global
 
		$wgParser->startExternalParse( $this->mTitle, new ParserOptions, OT_HTML, true );
 
		//FIXME: an EXTREMELY ugly hack to force generation of absolute links.
		//       this is needed because Title::getLocalUrl check wgRequest to see
		//       if absolute urls are requested, instead of it being a parser option.
		$_REQUEST['action'] = 'render';
 
		$renderer = BlurbRenderer::newFromArticle( $this, $wgParser );
		if (!$renderer) {
			wfDebug( "$fname: no feed found on page: " . $this->mTitle->getPrefixedText() . "\n" );
			wfHttpError(404, "Not Found", "no feed found on page: " . $this->mTitle->getPrefixedText() ); //TODO: better code & text
			return;
		}
 
		$description = ''; //TODO: grab from article content... but what? and how?
		$ts = time();
 
		//this also sends the right headers
		$xml = $renderer->renderFeed( $this->mFeedFormat, $description );
		wfDebug( "$fname: rendered feed \n" );
 
		$e = array( 'xml' => $xml, 'timestamp' => $ts );
		if ($ocache) {
			wfDebug( "$fname: caching feed ($cachekey) \n" );
			$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;';
		}
 
		wfDebug( "$fname: outputting fresh feed \n" );
		header('Content-Type: application/' . $this->mFeedFormat . '+xml; charset=UTF-8');
		print $xml;
		print "\n<!-- fresh: $note -->\n";
	}
 
	function purge() {
		$this->view( false );
	}
}
 
class RandomPageWithMinimumLength extends RandomPage {
    public function __construct ( $minimumLength = 0 ) {
        $this->extra = array ( 'page_len >= ' . $minimumLength );
        $this->namespaces = MWNamespace::getContentNamespaces();
        parent::__construct( 'Randompage' );
    }
}

Blurb.i18n.php[edit | edit source]

<?php
/**
 * Internationalization message file for Blurb Extension.
 *
 * @file
 * @ingroup Extensions
 */
 
$messages = array();
 
/** English
 * @author Leucosticte
 */
$messages['en'] = array(
	'blurbextension-desc' => 'Shows a blurb from a random page',
);
 
/** German (Deutsch)
 * @author Kghbln
 */
$messages['de'] = array(
	'blurbextension-desc' => 'Ermöglicht die Anzeige der Zusammenfassung einer zufällig angezeigten Seite',
);
 
/** Korean (한국어)
 * @author Cafeinlove
 */
$messages['ko'] = array(
        'blurbextension-desc' => '무작위로 선택된 문서의 일부분을 보여줍니다.',
);

Related Extensions[edit | edit source]

This extension is based on Extension:News.

See also[edit | edit source]