r67188 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r67187‎ | r67188 | r67189 >
Date:15:59, 1 June 2010
Author:daniel
Status:resolved (Comments)
Tags:
Comment:
adding DataTransclusion extension for showing data records from external sources. work in progress.
Modified paths:
  • /trunk/extensions/DataTransclusion (added) (history)
  • /trunk/extensions/DataTransclusion/DBDataTransclusionSource.php (added) (history)
  • /trunk/extensions/DataTransclusion/DataTransclusion.i18n.php (added) (history)
  • /trunk/extensions/DataTransclusion/DataTransclusion.php (added) (history)
  • /trunk/extensions/DataTransclusion/DataTransclusionHandler.php (added) (history)
  • /trunk/extensions/DataTransclusion/DataTransclusionSource.php (added) (history)

Diff [purge]

Index: trunk/extensions/DataTransclusion/DataTransclusion.i18n.php
@@ -0,0 +1,42 @@
 2+<?php
 3+/**
 4+ * Internationalisation file for the extension DataTransclusion
 5+ *
 6+ * @package MediaWiki
 7+ * @subpackage Extensions
 8+ * @author Daniel Kinzler for Wikimedia Deutschland
 9+ * @copyright © 2010 Wikimedia Deutschland (Author: Daniel Kinzler)
 10+ * @licence GNU General Public Licence 2.0 or later
 11+ */
 12+
 13+$messages = array();
 14+
 15+/** English
 16+ */
 17+$messages['en'] = array(
 18+ 'datatransclusion-desc' => 'Import and rendering of data records from external data sources',
 19+
 20+ 'datatransclusion-missing-source' => 'no data source specified (first argument is required)',
 21+ 'datatransclusion-unknown-source' => 'bad data source specified ($1 is not known)',
 22+ 'datatransclusion-bad-argument-by' => 'bad key field specified ($2 is not a key field in data source $1, valid keys are: $3)',
 23+ 'datatransclusion-missing-argument-key' => 'no key value specified (second or "key" argument is required)',
 24+ 'datatransclusion-missing-argument-template' => 'no template specified (third or "template" argument is required)',
 25+ 'datatransclusion-record-not-found' => 'no record matching $2 = $3 was found in data source $1',
 26+ 'datatransclusion-bad-template-name' => 'bad template name: $1',
 27+ 'datatransclusion-unknown-template' => '<nowiki>{{</nowiki>[[Template:$1|$1]]<nowiki>}}</nowiki> does not exist.',
 28+);
 29+
 30+/** Message documentation (Message documentation)
 31+ */
 32+$messages['qqq'] = array(
 33+ 'pageby-desc' => 'Shown in [[Special:Version]] as a short description of this extension. Do not translate links.',
 34+
 35+ 'datatransclusion-missing-source' => 'issued if no data source was specified.',
 36+ 'datatransclusion-unknown-source' => 'issued if an unknown data source was specified. $1 is the name of the data source.',
 37+ 'datatransclusion-bad-argument-by' => 'issued if a bad value was specified for the "by" argument, that is, an unknown key field was selected. $1 is the name of the data source, $2 is the value of the by argument, $3 is a list of all valid keys for this data source.',
 38+ 'datatransclusion-missing-argument-key' => 'issued if no "key" or second positional argument was given provided. A key value is always required.',
 39+ 'datatransclusion-missing-argument-template' => 'issued if no "template" or third positional argument was given provided. A target template is always required.',
 40+ 'datatransclusion-record-not-found' => 'issued if the record specified using the "by" and "key" arguments was nout found in the data source. $1 is the name of the data source, $2 is the key filed used, and $3 is the key value to select by.',
 41+ 'datatransclusion-bad-template-name' => 'issued if the template name specified is not valid. $1 is the given template name.',
 42+ 'datatransclusion-unknown-template' => 'issued if the template specified does not exist. $1 is the given template name.',
 43+);
Index: trunk/extensions/DataTransclusion/DataTransclusionHandler.php
@@ -0,0 +1,212 @@
 2+<?php
 3+/**
 4+ * handler code for DataTransclusion extension.
 5+ *
 6+ * @package MediaWiki
 7+ * @subpackage Extensions
 8+ * @author Daniel Kinzler for Wikimedia Deutschland
 9+ * @copyright © 2010 Wikimedia Deutschland (Author: Daniel Kinzler)
 10+ * @licence GNU General Public Licence 2.0 or later
 11+ */
 12+
 13+if( !defined( 'MEDIAWIKI' ) ) {
 14+ echo( "Not a valid entry point.\n" );
 15+ die( 1 );
 16+}
 17+
 18+class DataTransclusionHandler {
 19+ static function buildAssociativeArguments ( $params ) {
 20+ // build associative arguments from flat parameter list
 21+ $argv = array();
 22+ $i = 1;
 23+ foreach ( $params as $p ) {
 24+ if ( preg_match( '/^\s*(\S.*?)\s*=\s*(.*?)\s*$/', $p, $m ) ) {
 25+ $k = $m[1];
 26+ $v = preg_replace( '/^"\s*(.*?)\s*"$/', '$1', $m[2] ); // strip any quotes enclosing the value
 27+ }
 28+ else {
 29+ $v = trim( $p );
 30+ $k = $i;
 31+ $i += 1;
 32+ }
 33+
 34+ $argv[$k] = $v;
 35+ }
 36+
 37+ return $argv;
 38+ }
 39+
 40+ /**
 41+ * Entry point for the {{#record}} parser function.
 42+ * This is a wrapper around handleRecordTag
 43+ */
 44+ static function handleRecordFunction ( &$parser ) {
 45+ $params = func_get_args();
 46+ array_shift( $params ); // first is &$parser, strip it
 47+
 48+ // first user-supplied parameter must be category name
 49+ if ( !$params ) {
 50+ return ''; // no category specified, return nothing
 51+ }
 52+
 53+ // build associative arguments from flat parameter list
 54+ $argv = DataTransclusionHandler::buildAssociativeArguments( $params );
 55+
 56+ // now handle just like a <record> tag
 57+ $text = DataTransclusionHandler::handleRecordTag( null, $argv, $parser, false );
 58+ return array( $text, 'noparse' => false, 'isHTML' => false );
 59+ }
 60+
 61+ static function errorMessage( $key, $asHTML ) {
 62+ $params = func_get_args();
 63+ array_shift( $params ); // $key
 64+ array_shift( $params ); // $asHTML
 65+
 66+ if ( $asHTML ) $mode = 'parseinline';
 67+ else $mode = 'parsemag';
 68+
 69+ $m = wfMsgExt( $key, $mode, $params );
 70+ return "<span class=\"error\">$m</div>";
 71+ }
 72+
 73+ /**
 74+ * Entry point for the <record> tag parser hook.
 75+ */
 76+ static function handleRecordTag( $key, $argv, $parser = null, $asHTML = true ) {
 77+ //find out which data source to use...
 78+ if ( empty( $argv['source'] ) ) {
 79+ if ( empty( $argv[1] ) ) return DataTransclusionHandler::errorMessage( 'datatransclusion-missing-source', $asHTML );
 80+ else $sourceName = $argv[1];
 81+ } else {
 82+ $sourceName = $argv['source'];
 83+ }
 84+
 85+ $source = DataTransclusionHandler::getDataSource( $sourceName );
 86+ if ( empty( $source ) ) return DataTransclusionHandler::errorMessage( 'datatransclusion-unknown-source', $asHTML, $sourceName );
 87+
 88+ //find out how to find the desired record
 89+ if ( empty( $argv['by'] ) ) $by = $source->getDefaultKey();
 90+ else $by = $argv['by'];
 91+
 92+ $keyFields = $source->getKeyFields();
 93+ if ( ! in_array( $by, $keyFields ) ) return DataTransclusionHandler::errorMessage( 'datatransclusion-bad-argument-by', $asHTML, $sourceName, $by, join(', ', $keyFields) );
 94+
 95+ if ( !empty( $argv['key'] ) ) $key = $argv['key'];
 96+ else if ( $key === null || $key === false ) {
 97+ if ( empty( $argv[2] ) ) return DataTransclusionHandler::errorMessage( 'datatransclusion-missing-argument-key', $asHTML );
 98+ else $key = $argv[2];
 99+ }
 100+
 101+ //find out how to render the record
 102+ if ( empty( $argv['template'] ) ) {
 103+ if ( empty( $argv[3] ) ) return DataTransclusionHandler::errorMessage( 'datatransclusion-missing-argument-template', $asHTML );
 104+ else $template = $argv[3];
 105+ } else {
 106+ $template = $argv['template'];
 107+ }
 108+
 109+ //load the record
 110+ $record = $source->fetchRecord( $by, $key );
 111+ if ( empty( $record ) ) return DataTransclusionHandler::errorMessage( 'datatransclusion-record-not-found', $asHTML, $sourceName, $by, $key );
 112+
 113+ $record = DataTransclusionHandler::normalizeRecord( $record, $source );
 114+
 115+ //render the record into wiki text
 116+ $t = Title::newFromText( $template, NS_TEMPLATE );
 117+ if ( empty( $t ) ) return DataTransclusionHandler::errorMessage( 'datatransclusion-bad-template-name', $asHTML, $template );
 118+
 119+ $text = DataTransclusionHandler::renderTemplate( $parser, $t, $record );
 120+ if ( $text === false ) return DataTransclusionHandler::errorMessage( 'datatransclusion-unknown-template', $asHTML, $template );
 121+
 122+ //set parser output expiry
 123+ $expire = $source->getCacheDuration();
 124+ if ( $expire !== false && $expire !== null ) {
 125+ $parser->getOutput()->updateCacheExpiry( $expire ); //NOTE: this works only since r67185
 126+ }
 127+
 128+ if ( $asHTML && $parser ) { //render into HTML if desired
 129+ $html = $parser->recursiveTagParse( $text );
 130+ return $html;
 131+ } else {
 132+ return $text;
 133+ }
 134+ }
 135+
 136+ static function renderTemplate( &$parser, $title, $record ) {
 137+ $p = new Article( $title );
 138+ if ( !$p->exists() ) return false;
 139+
 140+ $text = $p->getContent();
 141+ $text = $parser->replaceVariables( $text, $record );
 142+
 143+ //FIXME: use cached & preparsed template. figure out how to...
 144+ /*
 145+ list( $text, $title ) = $parser->getTemplateDom( $title );
 146+ $frame = $parser->getPreprocessor()->newCustomFrame( $record );
 147+ $text = $frame->expand( $template );
 148+ */
 149+ return $text;
 150+ }
 151+
 152+ static function normalizeRecord( $record, $source ) {
 153+ $rec = array();
 154+
 155+ //keep record fields, add missing values
 156+ $fields = $source->getFieldNames();
 157+ foreach ( $fields as $f ) {
 158+ if ( isset( $record[ $f ] ) ) $v = $record[ $f ];
 159+ else $v = '';
 160+
 161+ $rec[ $f ] = $v;
 162+ }
 163+
 164+ //add source meta info, so we can render links back to the source,
 165+ //provide license info, etc
 166+ $info = $source->getSourceInfo();
 167+ foreach ( $info as $f => $v ) {
 168+ if ( is_array( $v ) || is_object( $v ) || is_resource( $v ) ) continue;
 169+ $rec[ "source.$f" ] = $v;
 170+ }
 171+
 172+ return $rec;
 173+ }
 174+
 175+
 176+ static function getDataSource( $name ) {
 177+ global $wgDataTransclusionSources;
 178+ if ( empty( $wgDataTransclusionSources[ $name ] ) ) return false;
 179+
 180+ $source = $wgDataTransclusionSources[ $name ];
 181+
 182+ if ( is_array( $source ) ) { //if the source is an array, use it to instantiate the sourece object
 183+ $spec = $source;
 184+ $spec[ 'name' ] = $name;
 185+
 186+ if ( !isset( $spec[ 'class' ] ) ) throw new MWException( "\$wgDataTransclusionSources['$name'] must specifying a class name in the 'class' field." );
 187+
 188+ $c = $spec[ 'class' ];
 189+ $obj = new $c( $spec ); //pass spec array as constructor argument
 190+ if ( !$obj ) throw new MWException( "failed to instantiate \$wgDataTransclusionSources['$name'] as new $c." );
 191+
 192+ $source = $obj;
 193+
 194+ if ( isset( $spec[ 'cache' ] ) ) { //check if a cache should be used
 195+ $c = $spec[ 'cache' ];
 196+ if ( !is_object( $c ) ) { //cache may be specified as a string
 197+ $c = wfGetCache( $c ); // $c should be one of the CACHE_* constants
 198+ }
 199+
 200+ $source = new CachingDataTransclusionSource( $obj, $c, @$spec['cache-duration'] ); //apply caching wrapper
 201+ }
 202+
 203+ $wgDataTransclusionSources[ $name ] = $source; //replace spec array by actual object, for later re-use
 204+ }
 205+
 206+ if ( !is_object( $source ) ) {
 207+ if ( !isset( $source[ 'class' ] ) ) throw new MWException( "\$wgDataTransclusionSources['$name'] must be an array or an object." );
 208+ }
 209+
 210+ return $source;
 211+ }
 212+
 213+}
Index: trunk/extensions/DataTransclusion/DataTransclusion.php
@@ -0,0 +1,43 @@
 2+<?php
 3+/**
 4+ * DataTransclusion extension - shows recent changes on a wiki page.
 5+ *
 6+ * @package MediaWiki
 7+ * @subpackage Extensions
 8+ * @author Daniel Kinzler for Wikimedia Deutschland
 9+ * @copyright © 2010 Wikimedia Deutschland (Author: Daniel Kinzler)
 10+ * @licence GNU General Public Licence 2.0 or later
 11+ */
 12+
 13+if( !defined( 'MEDIAWIKI' ) ) {
 14+ echo( "This file is an extension to the MediaWiki software and cannot be used standalone.\n" );
 15+ die( 1 );
 16+}
 17+
 18+$wgExtensionCredits['other'][] = array(
 19+ 'path' => __FILE__,
 20+ 'name' => 'DataTransclusion',
 21+ 'author' => 'Daniel Kinzler for Wikimedia Deutschland',
 22+ 'url' => 'http://mediawiki.org/wiki/Extension:DataTransclusion',
 23+ 'descriptionmsg' => 'datatransclusion-desc',
 24+);
 25+
 26+$dir = dirname(__FILE__) . '/';
 27+$wgExtensionMessagesFiles['DataTransclusion'] = $dir . 'DataTransclusion.i18n.php';
 28+
 29+$wgAutoloadClasses['DataTransclusionRenderer'] = $dir. 'DataTransclusionRenderer.php';
 30+$wgAutoloadClasses['DataTransclusionHandler'] = $dir. 'DataTransclusionHandler.php';
 31+$wgAutoloadClasses['DataTransclusionSource'] = $dir. 'DataTransclusionSource.php';
 32+$wgAutoloadClasses['CachingDataTransclusionSource'] = $dir. 'DataTransclusionSource.php';
 33+$wgAutoloadClasses['DBDataTransclusionSource'] = $dir. 'DBDataTransclusionSource.php';
 34+#$wgAutoloadClasses['WAPIDataTransclusionSource'] = $dir. 'WAPIDataTransclusionSource.php';
 35+
 36+$wgHooks['ParserFirstCallInit'][] = 'efDataTransclusionSetHooks';
 37+
 38+$wgDataTransclusionSources = array();
 39+
 40+function efDataTransclusionSetHooks( &$parser ) {
 41+ $parser->setHook( 'record' , 'DataTransclusionHandler::handleRecordTag' );
 42+ $parser->setFunctionHook( 'record' , 'DataTransclusionHandler::handleRecordFunction' ); #FIXME: this doesn't work.
 43+ return true;
 44+}
Index: trunk/extensions/DataTransclusion/DataTransclusionSource.php
@@ -0,0 +1,110 @@
 2+<?php
 3+/**
 4+ * DataTransclusion Source base class
 5+ *
 6+ * @package MediaWiki
 7+ * @subpackage Extensions
 8+ * @author Daniel Kinzler for Wikimedia Deutschland
 9+ * @copyright © 2010 Wikimedia Deutschland (Author: Daniel Kinzler)
 10+ * @licence GNU General Public Licence 2.0 or later
 11+ */
 12+
 13+if( !defined( 'MEDIAWIKI' ) ) {
 14+ echo( "This file is an extension to the MediaWiki software and cannot be used standalone.\n" );
 15+ die( 1 );
 16+}
 17+
 18+class DataTransclusionSource {
 19+ function __construct( $spec ) {
 20+ $this->sourceInfo = $spec;
 21+
 22+ $this->name = $spec[ 'name' ];
 23+
 24+ $this->keyFields = $spec[ 'keyFields' ];
 25+ if ( is_string( $this->keyFields ) ) $this->keyFields = preg_split( '!\s*[,;|]\s*!', $this->keyFields );
 26+
 27+ $this->fieldNames = $spec[ 'fieldNames' ];
 28+ if ( is_string( $this->fieldNames ) ) $this->fieldNames = preg_split( '!\s*[,;|]\s*!', $this->fieldNames );
 29+
 30+ if ( !empty( $spec[ 'defaultKey' ] ) ) $this->defaultKey = $spec[ 'defaultKey' ];
 31+ else $this->defaultKey = $this->keyFields[ 0 ];
 32+
 33+ if ( !empty( $spec[ 'cacheDuration' ] ) ) $this->cacheDuration = (int)$spec[ 'cacheDuration' ];
 34+ else $this->cacheDuration = null;
 35+ }
 36+
 37+ public function getName() {
 38+ return $this->name;
 39+ }
 40+
 41+ public function getDefaultKey() {
 42+ return $this->defaultKey;
 43+ }
 44+
 45+ public function getSourceInfo() {
 46+ return $this->sourceInfo;
 47+ }
 48+
 49+ public function getKeyFields() {
 50+ return $this->keyFields;
 51+ }
 52+
 53+ public function getFieldNames() {
 54+ return $this->fieldNames;
 55+ }
 56+
 57+ public function getCacheDuration() {
 58+ return $this->cacheDuration;
 59+ }
 60+
 61+ public function fetchRecord( $key, $value ) {
 62+ throw new MWException( "override fetchRecord()" );
 63+ }
 64+}
 65+
 66+class CachingDataTransclusionSource extends DataTransclusionSource {
 67+ function __construct( $source, $cache ) {
 68+ $this->source = $source;
 69+ $this->cache = $cache;
 70+ $this->duration = $duration;
 71+ }
 72+
 73+ public function getName() {
 74+ return $this->source->getName();
 75+ }
 76+
 77+ public function getDefaultTemplate() {
 78+ return $this->source->getDefaultTemplate();
 79+ }
 80+
 81+ public function getSourceInfo() {
 82+ return $this->source->getSourceInfo();
 83+ }
 84+
 85+ public function getKeyFields() {
 86+ return $this->source->getKeyFields();
 87+ }
 88+
 89+ public function getFieldNames() {
 90+ return $this->source->getFieldNames();
 91+ }
 92+
 93+ public function getCacheDuration() {
 94+ return $this->source->getCacheDuration();
 95+ }
 96+
 97+ public function fetchRecord( $key, $value ) {
 98+ global $wgDBname, $wgUser;
 99+
 100+ $cacheKey = "$wgDBname:DataTransclusion(" . $this->getName() . ":$key=$value)";
 101+
 102+ $rec = $this->cache->get( $cacheKey );
 103+
 104+ if ( !$rec ) {
 105+ $rec = $this->source->fetchRecord( $key, $value );
 106+ if ( $rec ) $this->cache->set( $cacheKey, $rev, $this->getCacheDuration() ) ; //XXX: also cache negatives??
 107+ }
 108+
 109+ return $rec;
 110+ }
 111+}
\ No newline at end of file
Index: trunk/extensions/DataTransclusion/DBDataTransclusionSource.php
@@ -0,0 +1,63 @@
 2+<?php
 3+/**
 4+ * DataTransclusion Source base class
 5+ *
 6+ * @package MediaWiki
 7+ * @subpackage Extensions
 8+ * @author Daniel Kinzler for Wikimedia Deutschland
 9+ * @copyright © 2010 Wikimedia Deutschland (Author: Daniel Kinzler)
 10+ * @licence GNU General Public Licence 2.0 or later
 11+ */
 12+
 13+if( !defined( 'MEDIAWIKI' ) ) {
 14+ echo( "This file is an extension to the MediaWiki software and cannot be used standalone.\n" );
 15+ die( 1 );
 16+}
 17+
 18+class DBDataTransclusionSource extends DataTransclusionSource {
 19+
 20+ function __construct( $spec ) {
 21+ if ( !isset( $spec[ 'keyFields' ] ) && isset( $spec[ 'keyTypes' ] ) ) $spec[ 'keyFields' ] = array_keys( $spec[ 'keyTypes' ] );
 22+
 23+ DataTransclusionSource::__construct( $spec );
 24+
 25+ $this->query = $spec[ 'query' ];
 26+
 27+ if ( isset( $spec[ 'keyTypes' ] ) ) $this->keyTypes = $spec[ 'keyTypes' ];
 28+ else $this->keyTypes = null;
 29+ }
 30+
 31+ public function convertKey( $key, $value ) {
 32+ if ( !isset( $this->keyTypes[ $key ] ) ) return (string)$value;
 33+
 34+ $t = strtolower( trim( $this->keyTypes[ $key ] ) );
 35+
 36+ if ( $t == 'int' ) return (int)$value;
 37+ else if ( $t == 'decimal' || $t == 'float' ) return (float)$value;
 38+ else return (string)$value;
 39+ }
 40+
 41+ public function fetchRecord( $field, $value ) {
 42+ $db = wfGetDB( DB_SLAVE );
 43+
 44+ if ( !preg_match( '/\w+[\w\d]+/', $field ) ) return false; // redundant, but make extra sure we don't get anythign evil here
 45+
 46+ $value = $this->convertKey( $field, $value );
 47+
 48+ if ( is_string( $value ) ) $v = $db->addQuotes( $value );
 49+ else $v = $value;
 50+
 51+ $where = "( " . $field . " = " . $v . " )";
 52+
 53+ if ( preg_match('/[)\s]WHERE[\s(]/is', $this->query ) ) $sql = $this->query . " AND " . $where;
 54+ else $sql = $this->query . " WHERE " . $where;
 55+
 56+ $rs = $db->query( $sql, "DBDataTransclusionSource(" . $this->getName() . ")::fetchRecord" );
 57+ if ( !$rs ) return false;
 58+
 59+ $rec = $db->fetchRow( $rs );
 60+
 61+ $db->freeResult( $rs );
 62+ return $rec;
 63+ }
 64+}

Comments

#Comment by MaxSem (talk | contribs)   16:24, 1 June 2010

Wrong message key 'pageby-desc' for qqq.

#Comment by Duesentrieb (talk | contribs)   06:58, 2 June 2010

fixed in r67214. thanks maxsem

Status & tagging log