3
if ( !defined( 'MEDIAWIKI' ) ) {
4
die( 'This file is a MediaWiki extension, it is not a valid entry point' );
7
$wgExtensionFunctions[] = 'wfSetupParserFunctions';
8
$wgExtensionCredits['parserhook'][] = array(
9
'name' => 'ParserFunctions',
11
'url' => 'http://www.mediawiki.org/wiki/Extension:ParserFunctions',
12
'author' => 'Tim Starling',
13
'description' => 'Enhance parser with logical functions',
14
'descriptionmsg' => 'pfunc_desc',
17
$wgExtensionMessagesFiles['ParserFunctions'] = dirname(__FILE__) . '/ParserFunctions.i18n.php';
18
$wgHooks['LanguageGetMagic'][] = 'wfParserFunctionsLanguageGetMagic';
20
$wgParserTestFiles[] = dirname( __FILE__ ) . "/funcsParserTests.txt";
22
class ExtParserFunctions {
24
var $mTimeCache = array();
26
var $mMaxTimeChars = 6000; # ~10 seconds
28
function registerParser( &$parser ) {
29
if ( defined( get_class( $parser ) . '::SFH_OBJECT_ARGS' ) ) {
30
// These functions accept DOM-style arguments
31
$parser->setFunctionHook( 'if', array( &$this, 'ifObj' ), SFH_OBJECT_ARGS );
32
$parser->setFunctionHook( 'ifeq', array( &$this, 'ifeqObj' ), SFH_OBJECT_ARGS );
33
$parser->setFunctionHook( 'switch', array( &$this, 'switchObj' ), SFH_OBJECT_ARGS );
34
$parser->setFunctionHook( 'ifexist', array( &$this, 'ifexistObj' ), SFH_OBJECT_ARGS );
35
$parser->setFunctionHook( 'ifexpr', array( &$this, 'ifexprObj' ), SFH_OBJECT_ARGS );
36
$parser->setFunctionHook( 'iferror', array( &$this, 'iferrorObj' ), SFH_OBJECT_ARGS );
38
$parser->setFunctionHook( 'if', array( &$this, 'ifHook' ) );
39
$parser->setFunctionHook( 'ifeq', array( &$this, 'ifeq' ) );
40
$parser->setFunctionHook( 'switch', array( &$this, 'switchHook' ) );
41
$parser->setFunctionHook( 'ifexist', array( &$this, 'ifexist' ) );
42
$parser->setFunctionHook( 'ifexpr', array( &$this, 'ifexpr' ) );
43
$parser->setFunctionHook( 'iferror', array( &$this, 'iferror' ) );
46
$parser->setFunctionHook( 'expr', array( &$this, 'expr' ) );
47
$parser->setFunctionHook( 'time', array( &$this, 'time' ) );
48
$parser->setFunctionHook( 'timel', array( &$this, 'localTime' ) );
49
$parser->setFunctionHook( 'rel2abs', array( &$this, 'rel2abs' ) );
50
$parser->setFunctionHook( 'titleparts', array( &$this, 'titleparts' ) );
55
function clearState(&$parser) {
56
$this->mTimeChars = 0;
57
$parser->pf_ifexist_breakdown = array();
61
function &getExprParser() {
62
if ( !isset( $this->mExprParser ) ) {
63
if ( !class_exists( 'ExprParser' ) ) {
64
require( dirname( __FILE__ ) . '/Expr.php' );
66
$this->mExprParser = new ExprParser;
68
return $this->mExprParser;
71
function expr( &$parser, $expr = '' ) {
73
return $this->getExprParser()->doExpression( $expr );
74
} catch(ExprError $e) {
75
return $e->getMessage();
79
function ifexpr( &$parser, $expr = '', $then = '', $else = '' ) {
81
if($this->getExprParser()->doExpression( $expr )) {
86
} catch (ExprError $e){
87
return $e->getMessage();
91
function ifexprObj( $parser, $frame, $args ) {
92
$expr = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
93
$then = isset( $args[1] ) ? $args[1] : '';
94
$else = isset( $args[2] ) ? $args[2] : '';
95
$result = $this->ifexpr( $parser, $expr, $then, $else );
96
if ( is_object( $result ) ) {
97
$result = trim( $frame->expand( $result ) );
102
function ifHook( &$parser, $test = '', $then = '', $else = '' ) {
103
if ( $test !== '' ) {
110
function ifObj( &$parser, $frame, $args ) {
111
$test = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
112
if ( $test !== '' ) {
113
return isset( $args[1] ) ? trim( $frame->expand( $args[1] ) ) : '';
115
return isset( $args[2] ) ? trim( $frame->expand( $args[2] ) ) : '';
119
function ifeq( &$parser, $left = '', $right = '', $then = '', $else = '' ) {
120
if ( $left == $right ) {
127
function ifeqObj( &$parser, $frame, $args ) {
128
$left = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
129
$right = isset( $args[1] ) ? trim( $frame->expand( $args[1] ) ) : '';
130
if ( $left == $right ) {
131
return isset( $args[2] ) ? trim( $frame->expand( $args[2] ) ) : '';
133
return isset( $args[3] ) ? trim( $frame->expand( $args[3] ) ) : '';
137
function iferror( &$parser, $test = '', $then = '', $else = false ) {
138
if ( preg_match( '/<(?:strong|span|p|div)\s(?:[^\s>]*\s+)*?class="(?:[^"\s>]*\s+)*?error(?:\s[^">]*)?"/', $test ) ) {
140
} elseif ( $else === false ) {
147
function iferrorObj( &$parser, $frame, $args ) {
148
$test = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
149
$then = isset( $args[1] ) ? $args[1] : false;
150
$else = isset( $args[2] ) ? $args[2] : false;
151
$result = $this->iferror( $parser, $test, $then, $else );
152
if ( $result === false ) {
155
return trim( $frame->expand( $result ) );
159
function switchHook( &$parser /*,...*/ ) {
160
$args = func_get_args();
161
array_shift( $args );
162
$primary = trim(array_shift($args));
166
$mwDefault =& MagicWord::get( 'default' );
167
foreach( $args as $arg ) {
168
$parts = array_map( 'trim', explode( '=', $arg, 2 ) );
169
if ( count( $parts ) == 2 ) {
171
if ( $found || $parts[0] == $primary ) {
172
# Found a match, return now
175
if ( $mwDefault->matchStartAndRemove( $parts[0] ) ) {
176
$default = $parts[1];
177
} # else wrong case, continue
179
} elseif ( count( $parts ) == 1 ) {
180
# Multiple input, single output
181
# If the value matches, set a flag and continue
182
if ( $parts[0] == $primary ) {
185
} # else RAM corruption due to cosmic ray?
188
# Check if the last item had no = sign, thus specifying the default case
189
if ( count( $parts ) == 1) {
191
} elseif ( !is_null( $default ) ) {
198
function switchObj( $parser, $frame, $args ) {
199
if ( count( $args ) == 0 ) {
202
$primary = trim( $frame->expand( array_shift( $args ) ) );
205
$lastItemHadNoEquals = false;
206
$mwDefault =& MagicWord::get( 'default' );
207
foreach ( $args as $arg ) {
208
$bits = $arg->splitArg();
209
$nameNode = $bits['name'];
210
$index = $bits['index'];
211
$valueNode = $bits['value'];
213
if ( $index === '' ) {
215
$lastItemHadNoEquals = false;
216
$test = trim( $frame->expand( $nameNode ) );
218
# Multiple input match
219
return trim( $frame->expand( $valueNode ) );
221
$test = trim( $frame->expand( $nameNode ) );
222
if ( $test == $primary ) {
223
# Found a match, return now
224
return trim( $frame->expand( $valueNode ) );
226
if ( $mwDefault->matchStartAndRemove( $test ) ) {
227
$default = $valueNode;
228
} # else wrong case, continue
232
# Multiple input, single output
233
# If the value matches, set a flag and continue
234
$lastItemHadNoEquals = true;
235
$test = trim( $frame->expand( $valueNode ) );
236
if ( $test == $primary ) {
242
# Check if the last item had no = sign, thus specifying the default case
243
if ( $lastItemHadNoEquals ) {
245
} elseif ( !is_null( $default ) ) {
246
return trim( $frame->expand( $default ) );
253
* Returns the absolute path to a subpage, relative to the current article
254
* title. Treats titles as slash-separated paths.
256
* Following subpage link syntax instead of standard path syntax, an
257
* initial slash is treated as a relative path, and vice versa.
259
public function rel2abs( &$parser , $to = '' , $from = '' ) {
263
$from = $parser->getTitle()->getPrefixedText();
266
$to = rtrim( $to , ' /' );
268
// if we have an empty path, or just one containing a dot
269
if( $to == '' || $to == '.' ) {
273
// if the path isn't relative
274
if ( substr( $to , 0 , 1) != '/' &&
275
substr( $to , 0 , 2) != './' &&
276
substr( $to , 0 , 3) != '../' &&
281
// Make a long path, containing both, enclose it in /.../
282
$fullPath = '/' . $from . '/' . $to . '/';
284
// remove redundant current path dots
285
$fullPath = preg_replace( '!/(\./)+!', '/', $fullPath );
287
// remove double slashes
288
$fullPath = preg_replace( '!/{2,}!', '/', $fullPath );
290
// remove the enclosing slashes now
291
$fullPath = trim( $fullPath , '/' );
292
$exploded = explode ( '/' , $fullPath );
293
$newExploded = array();
295
foreach ( $exploded as $current ) {
296
if( $current == '..' ) { // removing one level
297
if( !count( $newExploded ) ){
298
// attempted to access a node above root node
299
wfLoadExtensionMessages( 'ParserFunctions' );
300
return '<strong class="error">' . wfMsgForContent( 'pfunc_rel2abs_invalid_depth', $fullPath ) . '</strong>';
302
// remove last level from the stack
303
array_pop( $newExploded );
305
// add the current level to the stack
306
$newExploded[] = $current;
310
// we can now join it again
311
return implode( '/' , $newExploded );
314
function incrementIfexistCount( $parser, $frame ) {
315
// Don't let this be called more than a certain number of times. It tends to make the database explode.
316
global $wgExpensiveParserFunctionLimit;
317
$parser->mExpensiveFunctionCount++;
319
$pdbk = $frame->getPDBK( 1 );
320
if ( !isset( $parser->pf_ifexist_breakdown[$pdbk] ) ) {
321
$parser->pf_ifexist_breakdown[$pdbk] = 0;
323
$parser->pf_ifexist_breakdown[$pdbk] ++;
325
return $parser->mExpensiveFunctionCount <= $wgExpensiveParserFunctionLimit;
328
function ifexist( &$parser, $title = '', $then = '', $else = '' ) {
329
return $this->ifexistCommon( $parser, false, $title, $then, $else );
332
function ifexistCommon( &$parser, $frame, $title = '', $then = '', $else = '' ) {
333
$title = Title::newFromText( $title );
335
if( $title->getNamespace() == NS_MEDIA ) {
336
/* If namespace is specified as NS_MEDIA, then we want to
337
* check the physical file, not the "description" page.
339
if ( !$this->incrementIfexistCount( $parser, $frame ) ) {
342
$file = wfFindFile($title);
346
$parser->mOutput->addImage($file->getName());
347
return $file->exists() ? $then : $else;
348
} elseif( $title->getNamespace() == NS_SPECIAL ) {
349
/* Don't bother with the count for special pages,
350
* since their existence can be checked without
351
* accessing the database.
353
return SpecialPage::exists( $title->getDBkey() ) ? $then : $else;
354
} elseif( $title->isExternal() ) {
355
/* Can't check the existence of pages on other sites,
356
* so just return $else. Makes a sort of sense, since
357
* they don't exist _locally_.
361
$pdbk = $title->getPrefixedDBkey();
362
$lc = LinkCache::singleton();
363
if ( !$this->incrementIfexistCount( $parser, $frame ) ) {
366
if ( 0 != ( $id = $lc->getGoodLinkID( $pdbk ) ) ) {
367
$parser->mOutput->addLink( $title, $id );
369
} elseif ( $lc->isBadLink( $pdbk ) ) {
370
$parser->mOutput->addLink( $title, 0 );
373
$id = $title->getArticleID();
374
$parser->mOutput->addLink( $title, $id );
383
function ifexistObj( &$parser, $frame, $args ) {
384
$title = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
385
$then = isset( $args[1] ) ? $args[1] : null;
386
$else = isset( $args[2] ) ? $args[2] : null;
388
$result = $this->ifexistCommon( $parser, $frame, $title, $then, $else );
389
if ( $result === null ) {
392
return trim( $frame->expand( $result ) );
396
function time( &$parser, $format = '', $date = '', $local = false ) {
397
global $wgContLang, $wgLocaltimezone;
398
if ( isset( $this->mTimeCache[$format][$date][$local] ) ) {
399
return $this->mTimeCache[$format][$date][$local];
402
#compute the timestamp string $ts
403
#PHP >= 5.2 can handle dates before 1970 or after 2038 using the DateTime object
404
#PHP < 5.2 is limited to dates between 1970 and 2038
406
$invalidTime = false;
408
if ( class_exists( 'DateTime' ) ) { #PHP >= 5.2
409
try { #the DateTime constructor must be used because it throws exceptions when errors occur, whereas date_create appears to just output a warning that can't really be detected from within the code
410
if ( $date !== '' ) {
411
$dateObject = new DateTime( $date );
413
$dateObject = new DateTime(); #use current date and time
417
if ( isset( $wgLocaltimezone ) ) { #convert to MediaWiki local timezone if set
418
$dateObject->setTimeZone( new DateTimeZone( $wgLocaltimezone ) );
419
} #otherwise leave in PHP default
421
#if local time was not requested, convert to UTC
422
$dateObject->setTimeZone( new DateTimeZone( 'UTC' ) );
425
$ts = $dateObject->format( 'YmdHis' );
426
} catch (Exception $ex) {
430
if ( $date !== '' ) {
431
$unix = @strtotime( $date );
436
if ( $unix == -1 || $unix == false ) {
441
if ( isset( $wgLocaltimezone ) ) {
442
$oldtz = getenv( 'TZ' );
443
putenv( 'TZ='.$wgLocaltimezone );
445
wfSuppressWarnings(); // E_STRICT system time bitching
446
$ts = date( 'YmdHis', $unix );
448
if ( isset( $wgLocaltimezone ) ) {
449
putenv( 'TZ='.$oldtz );
452
$ts = wfTimestamp( TS_MW, $unix );
457
#format the timestamp and return the result
458
if ( $invalidTime ) {
459
wfLoadExtensionMessages( 'ParserFunctions' );
460
$result = '<strong class="error">' . wfMsgForContent( 'pfunc_time_error' ) . '</strong>';
462
$this->mTimeChars += strlen( $format );
463
if ( $this->mTimeChars > $this->mMaxTimeChars ) {
464
wfLoadExtensionMessages( 'ParserFunctions' );
465
return '<strong class="error">' . wfMsgForContent( 'pfunc_time_too_long' ) . '</strong>';
468
if ( method_exists( $wgContLang, 'sprintfDate' ) ) {
469
$result = $wgContLang->sprintfDate( $format, $ts );
471
if ( !class_exists( 'SprintfDateCompat' ) ) {
472
require( dirname( __FILE__ ) . '/SprintfDateCompat.php' );
475
$result = SprintfDateCompat::sprintfDate( $format, $ts );
479
$this->mTimeCache[$format][$date][$local] = $result;
483
function localTime( &$parser, $format = '', $date = '' ) {
484
return $this->time( $parser, $format, $date, true );
488
* Obtain a specified number of slash-separated parts of a title,
489
* e.g. {{#titleparts:Hello/World|1}} => "Hello"
491
* @param Parser $parser Parent parser
492
* @param string $title Title to split
493
* @param int $parts Number of parts to keep
494
* @param int $offset Offset starting at 1
497
public function titleparts( $parser, $title = '', $parts = 0, $offset = 0) {
498
$parts = intval( $parts );
499
$offset = intval( $offset );
500
$ntitle = Title::newFromText( $title );
501
if ( $ntitle instanceof Title ) {
502
$bits = explode( '/', $ntitle->getPrefixedText(), 25 );
503
if ( count( $bits ) <= 0 ) {
504
return $ntitle->getPrefixedText();
510
return implode( '/', array_slice( $bits, $offset ) );
512
return implode( '/', array_slice( $bits, $offset, $parts ) );
521
function wfSetupParserFunctions() {
522
global $wgParser, $wgExtParserFunctions, $wgHooks;
524
$wgExtParserFunctions = new ExtParserFunctions;
526
// Check for SFH_OBJECT_ARGS capability
527
if ( defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' ) ) {
528
$wgHooks['ParserFirstCallInit'][] = array( &$wgExtParserFunctions, 'registerParser' );
530
if ( class_exists( 'StubObject' ) && !StubObject::isRealObject( $wgParser ) ) {
531
$wgParser->_unstub();
533
$wgExtParserFunctions->registerParser( $wgParser );
536
$wgHooks['ParserClearState'][] = array( &$wgExtParserFunctions, 'clearState' );
539
function wfParserFunctionsLanguageGetMagic( &$magicWords, $langCode ) {
540
require_once( dirname( __FILE__ ) . '/ParserFunctions.i18n.magic.php' );
541
foreach( efParserFunctionsWords( $langCode ) as $word => $trans )
542
$magicWords[$word] = $trans;