Extension:Blurb

From MediaWiki.org
Jump to: navigation, search
MediaWiki extensions manual
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 GNU General Public License 2.0 or later
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 it is available at translatewiki.net

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]