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
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

Check usage and version matrix

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.

Contents

Installing [edit]

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]

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

Configuration [edit]

Configuration settings to define in LocalSettings.php:

$wgNewsFeedURLPattern [edit]

his 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]

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]

Defaults to 1024 * 4 characters.

$wgBlurbHeadScan [edit]

Defaults to 512 characters.

$wgBlurbMinimumLength [edit]

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

$wgBlurbSubstr [edit]

Defaults to 1024 characters.

$wgBlurbExclude [edit]

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

Code [edit]

Blurb.php [edit]

<?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]

<?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]

<?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',
);

Related Extensions [edit]

This extension is based on Extension:News.

See also [edit]