r51497 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r51496‎ | r51497 | r51498 >
Date:09:53, 5 June 2009
Author:tstarling
Status:deferred (Comments)
Tags:
Comment:
* Disabled string functions by default with a configuration variable. Outlined my case for doing so.
* Defer loading the bulk of the code until a parser function is actually called. Necessary due to the recent large increase in code size.
* Fixed the total disregard for parser state and object-oriented data flow in ExtParserFunctions::loadRegex().
Modified paths:
  • /trunk/extensions/ParserFunctions/ParserFunctions.php (modified) (history)
  • /trunk/extensions/ParserFunctions/ParserFunctions_body.php (added) (history)

Diff [purge]

Index: trunk/extensions/ParserFunctions/ParserFunctions_body.php
@@ -0,0 +1,769 @@
 2+<?php
 3+
 4+class ExtParserFunctions {
 5+ var $mExprParser;
 6+ var $mTimeCache = array();
 7+ var $mTimeChars = 0;
 8+ var $mMaxTimeChars = 6000; # ~10 seconds
 9+
 10+ function clearState(&$parser) {
 11+ $this->mTimeChars = 0;
 12+ $parser->pf_ifexist_breakdown = array();
 13+ $parser->pf_markerRegex = null;
 14+ return true;
 15+ }
 16+
 17+ /**
 18+ * Get the marker regex. Cached.
 19+ */
 20+ function getMarkerRegex( $parser ) {
 21+ if ( isset( $parser->pf_markerRegex ) ) {
 22+ return $parser->pf_markerRegex;
 23+ }
 24+
 25+ wfProfileIn( __METHOD__ );
 26+
 27+ $prefix = preg_quote( $parser->uniqPrefix(), '/' );
 28+
 29+ // The first line represents Parser from release 1.12 forward.
 30+ // subsequent lines are hacks to accomodate old Mediawiki versions.
 31+ if ( defined('Parser::MARKER_SUFFIX') )
 32+ $suffix = preg_quote( Parser::MARKER_SUFFIX, '/' );
 33+ elseif ( isset($parser->mMarkerSuffix) )
 34+ $suffix = preg_quote( $parser->mMarkerSuffix, '/' );
 35+ elseif ( defined('MW_PARSER_VERSION') &&
 36+ strcmp( MW_PARSER_VERSION, '1.6.1' ) > 0 )
 37+ $suffix = "QINU\x07";
 38+ else $suffix = 'QINU';
 39+
 40+ $parser->pf_markerRegex = '/' .$prefix. '(?:(?!' .$suffix. ').)*' . $suffix . '/us';
 41+
 42+ wfProfileOut( __METHOD__ );
 43+ return $parser->pf_markerRegex;
 44+ }
 45+
 46+ // Removes unique markers from passed parameters, used by string functions.
 47+ private function killMarkers ( $parser, $text ) {
 48+ return preg_replace( $this->getMarkerRegex( $parser ), '' , $text );
 49+ }
 50+
 51+ function &getExprParser() {
 52+ if ( !isset( $this->mExprParser ) ) {
 53+ if ( !class_exists( 'ExprParser' ) ) {
 54+ require( dirname( __FILE__ ) . '/Expr.php' );
 55+ }
 56+ $this->mExprParser = new ExprParser;
 57+ }
 58+ return $this->mExprParser;
 59+ }
 60+
 61+ function expr( &$parser, $expr = '' ) {
 62+ try {
 63+ return $this->getExprParser()->doExpression( $expr );
 64+ } catch(ExprError $e) {
 65+ return $e->getMessage();
 66+ }
 67+ }
 68+
 69+ function ifexpr( &$parser, $expr = '', $then = '', $else = '' ) {
 70+ try{
 71+ if($this->getExprParser()->doExpression( $expr )) {
 72+ return $then;
 73+ } else {
 74+ return $else;
 75+ }
 76+ } catch (ExprError $e){
 77+ return $e->getMessage();
 78+ }
 79+ }
 80+
 81+ function ifexprObj( $parser, $frame, $args ) {
 82+ $expr = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
 83+ $then = isset( $args[1] ) ? $args[1] : '';
 84+ $else = isset( $args[2] ) ? $args[2] : '';
 85+ $result = $this->ifexpr( $parser, $expr, $then, $else );
 86+ if ( is_object( $result ) ) {
 87+ $result = trim( $frame->expand( $result ) );
 88+ }
 89+ return $result;
 90+ }
 91+
 92+ function ifHook( &$parser, $test = '', $then = '', $else = '' ) {
 93+ if ( $test !== '' ) {
 94+ return $then;
 95+ } else {
 96+ return $else;
 97+ }
 98+ }
 99+
 100+ function ifObj( &$parser, $frame, $args ) {
 101+ $test = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
 102+ if ( $test !== '' ) {
 103+ return isset( $args[1] ) ? trim( $frame->expand( $args[1] ) ) : '';
 104+ } else {
 105+ return isset( $args[2] ) ? trim( $frame->expand( $args[2] ) ) : '';
 106+ }
 107+ }
 108+
 109+ function ifeq( &$parser, $left = '', $right = '', $then = '', $else = '' ) {
 110+ if ( $left == $right ) {
 111+ return $then;
 112+ } else {
 113+ return $else;
 114+ }
 115+ }
 116+
 117+ function ifeqObj( &$parser, $frame, $args ) {
 118+ $left = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
 119+ $right = isset( $args[1] ) ? trim( $frame->expand( $args[1] ) ) : '';
 120+ if ( $left == $right ) {
 121+ return isset( $args[2] ) ? trim( $frame->expand( $args[2] ) ) : '';
 122+ } else {
 123+ return isset( $args[3] ) ? trim( $frame->expand( $args[3] ) ) : '';
 124+ }
 125+ }
 126+
 127+ function iferror( &$parser, $test = '', $then = '', $else = false ) {
 128+ if ( preg_match( '/<(?:strong|span|p|div)\s(?:[^\s>]*\s+)*?class="(?:[^"\s>]*\s+)*?error(?:\s[^">]*)?"/', $test ) ) {
 129+ return $then;
 130+ } elseif ( $else === false ) {
 131+ return $test;
 132+ } else {
 133+ return $else;
 134+ }
 135+ }
 136+
 137+ function iferrorObj( &$parser, $frame, $args ) {
 138+ $test = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
 139+ $then = isset( $args[1] ) ? $args[1] : false;
 140+ $else = isset( $args[2] ) ? $args[2] : false;
 141+ $result = $this->iferror( $parser, $test, $then, $else );
 142+ if ( $result === false ) {
 143+ return '';
 144+ } else {
 145+ return trim( $frame->expand( $result ) );
 146+ }
 147+ }
 148+
 149+ function switchHook( &$parser /*,...*/ ) {
 150+ $args = func_get_args();
 151+ array_shift( $args );
 152+ $primary = trim(array_shift($args));
 153+ $found = false;
 154+ $parts = null;
 155+ $default = null;
 156+ $mwDefault =& MagicWord::get( 'default' );
 157+ foreach( $args as $arg ) {
 158+ $parts = array_map( 'trim', explode( '=', $arg, 2 ) );
 159+ if ( count( $parts ) == 2 ) {
 160+ # Found "="
 161+ if ( $found || $parts[0] == $primary ) {
 162+ # Found a match, return now
 163+ return $parts[1];
 164+ } else {
 165+ if ( $mwDefault->matchStartAndRemove( $parts[0] ) ) {
 166+ $default = $parts[1];
 167+ } # else wrong case, continue
 168+ }
 169+ } elseif ( count( $parts ) == 1 ) {
 170+ # Multiple input, single output
 171+ # If the value matches, set a flag and continue
 172+ if ( $parts[0] == $primary ) {
 173+ $found = true;
 174+ }
 175+ } # else RAM corruption due to cosmic ray?
 176+ }
 177+ # Default case
 178+ # Check if the last item had no = sign, thus specifying the default case
 179+ if ( count( $parts ) == 1) {
 180+ return $parts[0];
 181+ } elseif ( !is_null( $default ) ) {
 182+ return $default;
 183+ } else {
 184+ return '';
 185+ }
 186+ }
 187+
 188+ function switchObj( $parser, $frame, $args ) {
 189+ if ( count( $args ) == 0 ) {
 190+ return '';
 191+ }
 192+ $primary = trim( $frame->expand( array_shift( $args ) ) );
 193+ $found = false;
 194+ $default = null;
 195+ $lastItemHadNoEquals = false;
 196+ $mwDefault =& MagicWord::get( 'default' );
 197+ foreach ( $args as $arg ) {
 198+ $bits = $arg->splitArg();
 199+ $nameNode = $bits['name'];
 200+ $index = $bits['index'];
 201+ $valueNode = $bits['value'];
 202+
 203+ if ( $index === '' ) {
 204+ # Found "="
 205+ $lastItemHadNoEquals = false;
 206+ $test = trim( $frame->expand( $nameNode ) );
 207+ if ( $found ) {
 208+ # Multiple input match
 209+ return trim( $frame->expand( $valueNode ) );
 210+ } else {
 211+ $test = trim( $frame->expand( $nameNode ) );
 212+ if ( $test == $primary ) {
 213+ # Found a match, return now
 214+ return trim( $frame->expand( $valueNode ) );
 215+ } else {
 216+ if ( $mwDefault->matchStartAndRemove( $test ) ) {
 217+ $default = $valueNode;
 218+ } # else wrong case, continue
 219+ }
 220+ }
 221+ } else {
 222+ # Multiple input, single output
 223+ # If the value matches, set a flag and continue
 224+ $lastItemHadNoEquals = true;
 225+ $test = trim( $frame->expand( $valueNode ) );
 226+ if ( $test == $primary ) {
 227+ $found = true;
 228+ }
 229+ }
 230+ }
 231+ # Default case
 232+ # Check if the last item had no = sign, thus specifying the default case
 233+ if ( $lastItemHadNoEquals ) {
 234+ return $test;
 235+ } elseif ( !is_null( $default ) ) {
 236+ return trim( $frame->expand( $default ) );
 237+ } else {
 238+ return '';
 239+ }
 240+ }
 241+
 242+ /**
 243+ * Returns the absolute path to a subpage, relative to the current article
 244+ * title. Treats titles as slash-separated paths.
 245+ *
 246+ * Following subpage link syntax instead of standard path syntax, an
 247+ * initial slash is treated as a relative path, and vice versa.
 248+ */
 249+ public function rel2abs( &$parser , $to = '' , $from = '' ) {
 250+
 251+ $from = trim($from);
 252+ if( $from == '' ) {
 253+ $from = $parser->getTitle()->getPrefixedText();
 254+ }
 255+
 256+ $to = rtrim( $to , ' /' );
 257+
 258+ // if we have an empty path, or just one containing a dot
 259+ if( $to == '' || $to == '.' ) {
 260+ return $from;
 261+ }
 262+
 263+ // if the path isn't relative
 264+ if ( substr( $to , 0 , 1) != '/' &&
 265+ substr( $to , 0 , 2) != './' &&
 266+ substr( $to , 0 , 3) != '../' &&
 267+ $to != '..' )
 268+ {
 269+ $from = '';
 270+ }
 271+ // Make a long path, containing both, enclose it in /.../
 272+ $fullPath = '/' . $from . '/' . $to . '/';
 273+
 274+ // remove redundant current path dots
 275+ $fullPath = preg_replace( '!/(\./)+!', '/', $fullPath );
 276+
 277+ // remove double slashes
 278+ $fullPath = preg_replace( '!/{2,}!', '/', $fullPath );
 279+
 280+ // remove the enclosing slashes now
 281+ $fullPath = trim( $fullPath , '/' );
 282+ $exploded = explode ( '/' , $fullPath );
 283+ $newExploded = array();
 284+
 285+ foreach ( $exploded as $current ) {
 286+ if( $current == '..' ) { // removing one level
 287+ if( !count( $newExploded ) ){
 288+ // attempted to access a node above root node
 289+ wfLoadExtensionMessages( 'ParserFunctions' );
 290+ return '<strong class="error">' . wfMsgForContent( 'pfunc_rel2abs_invalid_depth', $fullPath ) . '</strong>';
 291+ }
 292+ // remove last level from the stack
 293+ array_pop( $newExploded );
 294+ } else {
 295+ // add the current level to the stack
 296+ $newExploded[] = $current;
 297+ }
 298+ }
 299+
 300+ // we can now join it again
 301+ return implode( '/' , $newExploded );
 302+ }
 303+
 304+ function incrementIfexistCount( $parser, $frame ) {
 305+ // Don't let this be called more than a certain number of times. It tends to make the database explode.
 306+ global $wgExpensiveParserFunctionLimit;
 307+ $parser->mExpensiveFunctionCount++;
 308+ if ( $frame ) {
 309+ $pdbk = $frame->getPDBK( 1 );
 310+ if ( !isset( $parser->pf_ifexist_breakdown[$pdbk] ) ) {
 311+ $parser->pf_ifexist_breakdown[$pdbk] = 0;
 312+ }
 313+ $parser->pf_ifexist_breakdown[$pdbk] ++;
 314+ }
 315+ return $parser->mExpensiveFunctionCount <= $wgExpensiveParserFunctionLimit;
 316+ }
 317+
 318+ function ifexist( &$parser, $title = '', $then = '', $else = '' ) {
 319+ return $this->ifexistCommon( $parser, false, $title, $then, $else );
 320+ }
 321+
 322+ function ifexistCommon( &$parser, $frame, $titletext = '', $then = '', $else = '' ) {
 323+ global $wgContLang;
 324+ $title = Title::newFromText( $titletext );
 325+ $wgContLang->findVariantLink( $titletext, $title, true );
 326+ if ( $title ) {
 327+ if( $title->getNamespace() == NS_MEDIA ) {
 328+ /* If namespace is specified as NS_MEDIA, then we want to
 329+ * check the physical file, not the "description" page.
 330+ */
 331+ if ( !$this->incrementIfexistCount( $parser, $frame ) ) {
 332+ return $else;
 333+ }
 334+ $file = wfFindFile($title);
 335+ if ( !$file ) {
 336+ return $else;
 337+ }
 338+ $parser->mOutput->addImage($file->getName());
 339+ return $file->exists() ? $then : $else;
 340+ } elseif( $title->getNamespace() == NS_SPECIAL ) {
 341+ /* Don't bother with the count for special pages,
 342+ * since their existence can be checked without
 343+ * accessing the database.
 344+ */
 345+ return SpecialPage::exists( $title->getDBkey() ) ? $then : $else;
 346+ } elseif( $title->isExternal() ) {
 347+ /* Can't check the existence of pages on other sites,
 348+ * so just return $else. Makes a sort of sense, since
 349+ * they don't exist _locally_.
 350+ */
 351+ return $else;
 352+ } else {
 353+ $pdbk = $title->getPrefixedDBkey();
 354+ $lc = LinkCache::singleton();
 355+ if ( !$this->incrementIfexistCount( $parser, $frame ) ) {
 356+ return $else;
 357+ }
 358+ if ( 0 != ( $id = $lc->getGoodLinkID( $pdbk ) ) ) {
 359+ $parser->mOutput->addLink( $title, $id );
 360+ return $then;
 361+ } elseif ( $lc->isBadLink( $pdbk ) ) {
 362+ $parser->mOutput->addLink( $title, 0 );
 363+ return $else;
 364+ }
 365+ $id = $title->getArticleID();
 366+ $parser->mOutput->addLink( $title, $id );
 367+ if ( $id ) {
 368+ return $then;
 369+ }
 370+ }
 371+ }
 372+ return $else;
 373+ }
 374+
 375+ function ifexistObj( &$parser, $frame, $args ) {
 376+ $title = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
 377+ $then = isset( $args[1] ) ? $args[1] : null;
 378+ $else = isset( $args[2] ) ? $args[2] : null;
 379+
 380+ $result = $this->ifexistCommon( $parser, $frame, $title, $then, $else );
 381+ if ( $result === null ) {
 382+ return '';
 383+ } else {
 384+ return trim( $frame->expand( $result ) );
 385+ }
 386+ }
 387+
 388+ function time( &$parser, $format = '', $date = '', $local = false ) {
 389+ global $wgContLang, $wgLocaltimezone;
 390+ if ( isset( $this->mTimeCache[$format][$date][$local] ) ) {
 391+ return $this->mTimeCache[$format][$date][$local];
 392+ }
 393+
 394+ #compute the timestamp string $ts
 395+ #PHP >= 5.2 can handle dates before 1970 or after 2038 using the DateTime object
 396+ #PHP < 5.2 is limited to dates between 1970 and 2038
 397+
 398+ $invalidTime = false;
 399+
 400+ if ( class_exists( 'DateTime' ) ) { #PHP >= 5.2
 401+ # the DateTime constructor must be used because it throws exceptions
 402+ # when errors occur, whereas date_create appears to just output a warning
 403+ # that can't really be detected from within the code
 404+ try {
 405+ # Determine timezone
 406+ if ( $local ) {
 407+ # convert to MediaWiki local timezone if set
 408+ if ( isset( $wgLocaltimezone ) ) {
 409+ $tz = new DateTimeZone( $wgLocaltimezone );
 410+ } else {
 411+ $tz = new DateTimeZone( 'UTC' );
 412+ }
 413+ } else {
 414+ # if local time was not requested, convert to UTC
 415+ $tz = new DateTimeZone( 'UTC' );
 416+ }
 417+
 418+ # Parse date
 419+ if ( $date !== '' ) {
 420+ $dateObject = new DateTime( $date, $tz );
 421+ } else {
 422+ # use current date and time
 423+ $dateObject = new DateTime( 'now', $tz );
 424+ }
 425+
 426+ # Generate timestamp
 427+ $ts = $dateObject->format( 'YmdHis' );
 428+ } catch (Exception $ex) {
 429+ $invalidTime = true;
 430+ }
 431+ } else { #PHP < 5.2
 432+ if ( $date !== '' ) {
 433+ $unix = @strtotime( $date );
 434+ } else {
 435+ $unix = time();
 436+ }
 437+
 438+ if ( $unix == -1 || $unix == false ) {
 439+ $invalidTime = true;
 440+ } else {
 441+ if ( $local ) {
 442+ # Use the time zone
 443+ if ( isset( $wgLocaltimezone ) ) {
 444+ $oldtz = getenv( 'TZ' );
 445+ putenv( 'TZ='.$wgLocaltimezone );
 446+ }
 447+ wfSuppressWarnings(); // E_STRICT system time bitching
 448+ $ts = date( 'YmdHis', $unix );
 449+ wfRestoreWarnings();
 450+ if ( isset( $wgLocaltimezone ) ) {
 451+ putenv( 'TZ='.$oldtz );
 452+ }
 453+ } else {
 454+ $ts = wfTimestamp( TS_MW, $unix );
 455+ }
 456+ }
 457+ }
 458+
 459+ #format the timestamp and return the result
 460+ if ( $invalidTime ) {
 461+ wfLoadExtensionMessages( 'ParserFunctions' );
 462+ $result = '<strong class="error">' . wfMsgForContent( 'pfunc_time_error' ) . '</strong>';
 463+ } else {
 464+ $this->mTimeChars += strlen( $format );
 465+ if ( $this->mTimeChars > $this->mMaxTimeChars ) {
 466+ wfLoadExtensionMessages( 'ParserFunctions' );
 467+ return '<strong class="error">' . wfMsgForContent( 'pfunc_time_too_long' ) . '</strong>';
 468+ } else {
 469+
 470+ if ( method_exists( $wgContLang, 'sprintfDate' ) ) {
 471+ $result = $wgContLang->sprintfDate( $format, $ts );
 472+ } else {
 473+ if ( !class_exists( 'SprintfDateCompat' ) ) {
 474+ require( dirname( __FILE__ ) . '/SprintfDateCompat.php' );
 475+ }
 476+
 477+ $result = SprintfDateCompat::sprintfDate( $format, $ts );
 478+ }
 479+ }
 480+ }
 481+ $this->mTimeCache[$format][$date][$local] = $result;
 482+ return $result;
 483+ }
 484+
 485+ function localTime( &$parser, $format = '', $date = '' ) {
 486+ return $this->time( $parser, $format, $date, true );
 487+ }
 488+
 489+ /**
 490+ * Obtain a specified number of slash-separated parts of a title,
 491+ * e.g. {{#titleparts:Hello/World|1}} => "Hello"
 492+ *
 493+ * @param Parser $parser Parent parser
 494+ * @param string $title Title to split
 495+ * @param int $parts Number of parts to keep
 496+ * @param int $offset Offset starting at 1
 497+ * @return string
 498+ */
 499+ public function titleparts( $parser, $title = '', $parts = 0, $offset = 0) {
 500+ $parts = intval( $parts );
 501+ $offset = intval( $offset );
 502+ $ntitle = Title::newFromText( $title );
 503+ if ( $ntitle instanceof Title ) {
 504+ $bits = explode( '/', $ntitle->getPrefixedText(), 25 );
 505+ if ( count( $bits ) <= 0 ) {
 506+ return $ntitle->getPrefixedText();
 507+ } else {
 508+ if ( $offset > 0 ) {
 509+ --$offset;
 510+ }
 511+ if ( $parts == 0 ) {
 512+ return implode( '/', array_slice( $bits, $offset ) );
 513+ } else {
 514+ return implode( '/', array_slice( $bits, $offset, $parts ) );
 515+ }
 516+ }
 517+ } else {
 518+ return $title;
 519+ }
 520+ }
 521+
 522+ // Verifies parameter is less than max string length.
 523+ private function checkLength( $text ) {
 524+ global $wgPFStringLengthLimit;
 525+ return ( mb_strlen( $text ) < $wgPFStringLengthLimit );
 526+ }
 527+
 528+ // Generates error message. Called when string is too long.
 529+ private function tooLongError() {
 530+ global $wgPFStringLengthLimit, $wgContLang;
 531+ wfLoadExtensionMessages( 'ParserFunctions' );
 532+
 533+ return '<strong class="error">' .
 534+ wfMsgExt( 'pfunc_string_too_long',
 535+ array( 'escape', 'parsemag', 'content' ),
 536+ $wgContLang->formatNum( $wgPFStringLengthLimit ) ) .
 537+ '</strong>';
 538+ }
 539+
 540+ /**
 541+ * {{#len:string}}
 542+ *
 543+ * Reports number of characters in string.
 544+ */
 545+ function runLen ( &$parser, $inStr = '' ) {
 546+ wfProfileIn( __METHOD__ );
 547+
 548+ $inStr = $this->killMarkers( $parser, (string)$inStr );
 549+ $len = mb_strlen( $inStr );
 550+
 551+ wfProfileOut( __METHOD__ );
 552+ return $len;
 553+ }
 554+
 555+ /**
 556+ * {{#pos: string | needle | offset}}
 557+ *
 558+ * Finds first occurrence of "needle" in "string" starting at "offset".
 559+ *
 560+ * Note: If the needle is an empty string, single space is used instead.
 561+ * Note: If the needle is not found, empty string is returned.
 562+ */
 563+ function runPos ( &$parser, $inStr = '', $inNeedle = '', $inOffset = 0 ) {
 564+ wfProfileIn( __METHOD__ );
 565+
 566+ $inStr = $this->killMarkers( (string)$inStr );
 567+ $inNeedle = $this->killMarkers( (string)$inNeedle );
 568+
 569+ if( !$this->checkLength( $inStr ) ||
 570+ !$this->checkLength( $inNeedle ) ) {
 571+ wfProfileOut( __METHOD__ );
 572+ return $this->tooLongError();
 573+ }
 574+
 575+ if( $inNeedle == '' ) { $inNeedle = ' '; }
 576+
 577+ $pos = mb_strpos( $inStr, $inNeedle, $inOffset );
 578+ if( $pos === false ) { $pos = ""; }
 579+
 580+ wfProfileOut( __METHOD__ );
 581+ return $pos;
 582+ }
 583+
 584+ /**
 585+ * {{#rpos: string | needle}}
 586+ *
 587+ * Finds last occurrence of "needle" in "string".
 588+ *
 589+ * Note: If the needle is an empty string, single space is used instead.
 590+ * Note: If the needle is not found, -1 is returned.
 591+ */
 592+ function runRPos ( &$parser, $inStr = '', $inNeedle = '' ) {
 593+ wfProfileIn( __METHOD__ );
 594+
 595+ $inStr = $this->killMarkers( (string)$inStr );
 596+ $inNeedle = $this->killMarkers( (string)$inNeedle );
 597+
 598+ if( !$this->checkLength( $inStr ) ||
 599+ !$this->checkLength( $inNeedle ) ) {
 600+ wfProfileOut( __METHOD__ );
 601+ return $this->tooLongError();
 602+ }
 603+
 604+ if( $inNeedle == '' ) { $inNeedle = ' '; }
 605+
 606+ $pos = mb_strrpos( $inStr, $inNeedle );
 607+ if( $pos === false ) { $pos = -1; }
 608+
 609+ wfProfileOut( __METHOD__ );
 610+ return $pos;
 611+ }
 612+
 613+ /**
 614+ * {{#sub: string | start | length }}
 615+ *
 616+ * Returns substring of "string" starting at "start" and having
 617+ * "length" characters.
 618+ *
 619+ * Note: If length is zero, the rest of the input is returned.
 620+ * Note: A negative value for "start" operates from the end of the
 621+ * "string".
 622+ * Note: A negative value for "length" returns a string reduced in
 623+ * length by that amount.
 624+ */
 625+ function runSub ( &$parser, $inStr = '', $inStart = 0, $inLength = 0 ) {
 626+ wfProfileIn( __METHOD__ );
 627+
 628+ $inStr = $this->killMarkers( (string)$inStr );
 629+
 630+ if( !$this->checkLength( $inStr ) ) {
 631+ wfProfileOut( __METHOD__ );
 632+ return $this->tooLongError();
 633+ }
 634+
 635+ if ( intval($inLength) == 0 ) {
 636+ $result = mb_substr( $inStr, $inStart );
 637+ } else {
 638+ $result = mb_substr( $inStr, $inStart, $inLength );
 639+ }
 640+
 641+ wfProfileOut( __METHOD__ );
 642+ return $result;
 643+ }
 644+
 645+ /**
 646+ * {{#count: string | substr }}
 647+ *
 648+ * Returns number of occurrences of "substr" in "string".
 649+ *
 650+ * Note: If "substr" is empty, a single space is used.
 651+ */
 652+ function runCount ( &$parser, $inStr = '', $inSubStr = '' ) {
 653+ wfProfileIn( __METHOD__ );
 654+
 655+ $inStr = $this->killMarkers( (string)$inStr );
 656+ $inSubStr = $this->killMarkers( (string)$inSubStr );
 657+
 658+ if( !$this->checkLength( $inStr ) ||
 659+ !$this->checkLength( $inSubStr ) ) {
 660+ wfProfileOut( __METHOD__ );
 661+ return $this->tooLongError();
 662+ }
 663+
 664+ if( $inSubStr == '' ) { $inSubStr = ' '; }
 665+
 666+ $result = mb_substr_count( $inStr, $inSubStr );
 667+
 668+ wfProfileOut( __METHOD__ );
 669+ return $result;
 670+ }
 671+
 672+ /**
 673+ * {{#replace:string | from | to | limit }}
 674+ *
 675+ * Replaces each occurrence of "from" in "string" with "to".
 676+ * At most "limit" replacements are performed.
 677+ *
 678+ * Note: Armored against replacements that would generate huge strings.
 679+ * Note: If "from" is an empty string, single space is used instead.
 680+ */
 681+ function runReplace( &$parser, $inStr = '',
 682+ $inReplaceFrom = '', $inReplaceTo = '', $inLimit = -1 ) {
 683+ global $wgPFStringLengthLimit;
 684+ wfProfileIn( __METHOD__ );
 685+
 686+ $inStr = $this->killMarkers( (string)$inStr );
 687+ $inReplaceFrom = $this->killMarkers( (string)$inReplaceFrom );
 688+ $inReplaceTo = $this->killMarkers( (string)$inReplaceTo );
 689+
 690+ if( !$this->checkLength( $inStr ) ||
 691+ !$this->checkLength( $inReplaceFrom ) ||
 692+ !$this->checkLength( $inReplaceTo ) ) {
 693+ wfProfileOut( __METHOD__ );
 694+ return $this->tooLongError();
 695+ }
 696+
 697+ if( $inReplaceFrom == '' ) { $inReplaceFrom = ' '; }
 698+
 699+ // Precompute limit to avoid generating enormous string:
 700+ $diff = mb_strlen( $inReplaceTo ) - mb_strlen( $inReplaceFrom );
 701+ if( $diff > 0 ) {
 702+ $limit = ( ( $wgPFStringLengthLimit - mb_strlen( $inStr ) ) / $diff ) + 1;
 703+ } else {
 704+ $limit = -1;
 705+ }
 706+
 707+ $inLimit = intval($inLimit);
 708+ if( $inLimit >= 0 ) {
 709+ if( $limit > $inLimit || $limit == -1 ) { $limit = $inLimit; }
 710+ }
 711+
 712+ // Use regex to allow limit and handle UTF-8 correctly.
 713+ $inReplaceFrom = preg_quote( $inReplaceFrom, '/' );
 714+ $inReplaceTo = preg_quote( $inReplaceTo, '/' );
 715+
 716+ $result = preg_replace( '/' . $inReplaceFrom . '/u',
 717+ $inReplaceTo, $inStr, $limit);
 718+
 719+ if( !$this->checkLength( $result ) ) {
 720+ wfProfileOut( __METHOD__ );
 721+ return $this->tooLongError();
 722+ }
 723+
 724+ wfProfileOut( __METHOD__ );
 725+ return $result;
 726+ }
 727+
 728+
 729+ /**
 730+ * {{#explode:string | delimiter | position}}
 731+ *
 732+ * Breaks "string" into chunks separated by "delimiter" and returns the
 733+ * chunk identified by "position".
 734+ *
 735+ * Note: Negative position can be used to specify tokens from the end.
 736+ * Note: If the divider is an empty string, single space is used instead.
 737+ * Note: Empty string is returned if there are not enough exploded chunks.
 738+ */
 739+ function runExplode ( &$parser, $inStr = '', $inDiv = '', $inPos = 0 ) {
 740+ wfProfileIn( __METHOD__ );
 741+
 742+ $inStr = $this->killMarkers( (string)$inStr );
 743+ $inDiv = $this->killMarkers( (string)$inDiv );
 744+
 745+ if( $inDiv == '' ) { $inDiv = ' '; }
 746+
 747+ if( !$this->checkLength( $inStr ) ||
 748+ !$this->checkLength( $inDiv ) ) {
 749+ wfProfileOut( __METHOD__ );
 750+ return $this->tooLongError();
 751+ }
 752+
 753+ $inStr = preg_quote( $inStr, '/' );
 754+ $inDiv = preg_quote( $inDiv, '/' );
 755+
 756+ $matches = preg_split( '/'.$inDiv.'/u', $inStr );
 757+
 758+ if( $inPos >= 0 && isset( $matches[$inPos] ) ) {
 759+ $result = $matches[$inPos];
 760+ } elseif ( $inPos < 0 && isset( $matches[count($matches) + $inPos] ) ) {
 761+ $result = $matches[count($matches) + $inPos];
 762+ } else {
 763+ $result = '';
 764+ }
 765+
 766+ wfProfileOut( __METHOD__ );
 767+ return $result;
 768+ }
 769+}
 770+
Property changes on: trunk/extensions/ParserFunctions/ParserFunctions_body.php
___________________________________________________________________
Added: svn:eol-style
1771 + native
Index: trunk/extensions/ParserFunctions/ParserFunctions.php
@@ -4,6 +4,34 @@
55 die( 'This file is a MediaWiki extension, it is not a valid entry point' );
66 }
77
 8+
 9+/**
 10+ * CONFIGURATION
 11+ * These variables may be overridden in LocalSettings.php after you include the
 12+ * extension file.
 13+ */
 14+
 15+/**
 16+ * Defines the maximum length of a string that string functions are allowed to operate on
 17+ * Prevention against denial of service by string function abuses.
 18+ */
 19+$wgPFStringLengthLimit = 1000;
 20+
 21+/**
 22+ * Enable string functions.
 23+ *
 24+ * Set this to true if you want your users to be able to implement their own
 25+ * parsers in the ugliest, most inefficient programming language known to man:
 26+ * MediaWiki wikitext with ParserFunctions.
 27+ *
 28+ * WARNING: enabling this may have an adverse impact on the sanity of your users.
 29+ * An alternative, saner solution for embedding complex text processing in
 30+ * MediaWiki templates can be found at: http://www.mediawiki.org/wiki/Extension:Lua
 31+ */
 32+$wgPFEnableStringFunctions = false;
 33+
 34+
 35+/** REGISTRATION */
836 $wgExtensionFunctions[] = 'wfSetupParserFunctions';
937 $wgExtensionCredits['parserhook'][] = array(
1038 'path' => __FILE__,
@@ -15,27 +43,48 @@
1644 'descriptionmsg' => 'pfunc_desc',
1745 );
1846
 47+$wgAutoloadClasses['ExtParserFunctions'] = dirname(__FILE__).'/ParserFunctions_body.php';
1948 $wgExtensionMessagesFiles['ParserFunctions'] = dirname(__FILE__) . '/ParserFunctions.i18n.php';
2049 $wgHooks['LanguageGetMagic'][] = 'wfParserFunctionsLanguageGetMagic';
21 -$wgHooks['ParserAfterStrip'][] = 'ExtParserFunctions::loadRegex';
2250
2351 $wgParserTestFiles[] = dirname( __FILE__ ) . "/funcsParserTests.txt";
2452
25 -//Defines the maximum length of a string that string functions are allowed to operate on
26 -//Prevention against denial of service by string function abuses.
27 -if( !isset($wgStringFunctionsLimit) ) {
28 - $wgStringFunctionsLimit = 1000;
 53+
 54+function wfSetupParserFunctions() {
 55+ global $wgParser, $wgPFHookStub, $wgHooks;
 56+
 57+ $wgPFHookStub = new ParserFunctions_HookStub;
 58+
 59+ // Check for SFH_OBJECT_ARGS capability
 60+ if ( defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' ) ) {
 61+ $wgHooks['ParserFirstCallInit'][] = array( &$wgPFHookStub, 'registerParser' );
 62+ } else {
 63+ if ( class_exists( 'StubObject' ) && !StubObject::isRealObject( $wgParser ) ) {
 64+ $wgParser->_unstub();
 65+ }
 66+ $wgPFHookStub->registerParser( $wgParser );
 67+ }
 68+
 69+ $wgHooks['ParserClearState'][] = array( &$wgPFHookStub, 'clearState' );
2970 }
3071
31 -class ExtParserFunctions {
32 - var $mExprParser;
33 - var $mTimeCache = array();
34 - var $mTimeChars = 0;
35 - var $mMaxTimeChars = 6000; # ~10 seconds
 72+function wfParserFunctionsLanguageGetMagic( &$magicWords, $langCode ) {
 73+ require_once( dirname( __FILE__ ) . '/ParserFunctions.i18n.magic.php' );
 74+ foreach( efParserFunctionsWords( $langCode ) as $word => $trans )
 75+ $magicWords[$word] = $trans;
 76+ return true;
 77+}
3678
37 - static $markerRegex = false;
 79+/**
 80+ * Stub class to defer loading of the bulk of the code until a parser function is
 81+ * actually used.
 82+ */
 83+class ParserFunctions_HookStub {
 84+ var $realObj;
3885
3986 function registerParser( &$parser ) {
 87+ global $wgPFEnableStringFunctions;
 88+
4089 if ( defined( get_class( $parser ) . '::SFH_OBJECT_ARGS' ) ) {
4190 // These functions accept DOM-style arguments
4291 $parser->setFunctionHook( 'if', array( &$this, 'ifObj' ), SFH_OBJECT_ARGS );
@@ -60,798 +109,33 @@
61110 $parser->setFunctionHook( 'titleparts', array( &$this, 'titleparts' ) );
62111
63112 //String Functions
64 - $parser->setFunctionHook( 'len', array(&$this, 'runLen' ));
65 - $parser->setFunctionHook( 'pos', array(&$this, 'runPos' ));
66 - $parser->setFunctionHook( 'rpos', array(&$this, 'runRPos' ));
67 - $parser->setFunctionHook( 'sub', array(&$this, 'runSub' ));
68 - $parser->setFunctionHook( 'count', array(&$this, 'runCount' ));
69 - $parser->setFunctionHook( 'replace', array(&$this, 'runReplace' ));
70 - $parser->setFunctionHook( 'explode', array(&$this, 'runExplode' ));
 113+ if ( $wgPFEnableStringFunctions ) {
 114+ $parser->setFunctionHook( 'len', array(&$this, 'runLen' ));
 115+ $parser->setFunctionHook( 'pos', array(&$this, 'runPos' ));
 116+ $parser->setFunctionHook( 'rpos', array(&$this, 'runRPos' ));
 117+ $parser->setFunctionHook( 'sub', array(&$this, 'runSub' ));
 118+ $parser->setFunctionHook( 'count', array(&$this, 'runCount' ));
 119+ $parser->setFunctionHook( 'replace', array(&$this, 'runReplace' ));
 120+ $parser->setFunctionHook( 'explode', array(&$this, 'runExplode' ));
 121+ }
71122
72123 return true;
73124 }
74125
75 - function clearState(&$parser) {
76 - $this->mTimeChars = 0;
77 - $parser->pf_ifexist_breakdown = array();
 126+ /** Defer ParserClearState */
 127+ function clearState( &$parser ) {
 128+ if ( !is_null( $this->realObj ) ) {
 129+ $this->realObj->clearState( $parser );
 130+ }
78131 return true;
79132 }
80133
81 - /* Called by ParserAfterStrip. Preloads the syntax for unique markers
82 - so that we can avoid reconstructing it on every operation. */
83 - static function loadRegex( &$parser ) {
84 - wfProfileIn( __METHOD__ );
85 -
86 - $prefix = preg_quote( $parser->uniqPrefix(), '/' );
87 -
88 - // The first line represents Parser from release 1.12 forward.
89 - // subsequent lines are hacks to accomodate old Mediawiki versions.
90 - if ( defined('Parser::MARKER_SUFFIX') )
91 - $suffix = preg_quote( Parser::MARKER_SUFFIX, '/' );
92 - elseif ( isset($parser->mMarkerSuffix) )
93 - $suffix = preg_quote( $parser->mMarkerSuffix, '/' );
94 - elseif ( defined('MW_PARSER_VERSION') &&
95 - strcmp( MW_PARSER_VERSION, '1.6.1' ) > 0 )
96 - $suffix = "QINU\x07";
97 - else $suffix = 'QINU';
98 -
99 - self::$markerRegex = '/' .$prefix. '(?:(?!' .$suffix. ').)*' . $suffix . '/us';
100 -
101 - wfProfileOut( __METHOD__ );
102 - return true;
103 - }
104 -
105 - // Removes unique markers from passed parameters, used by string functions.
106 - private function killMarkers ( $text ) {
107 - if( self::$markerRegex ) {
108 - return preg_replace( self::$markerRegex , '' , $text );
109 - } else {
110 - return $text;
 134+ /** Pass through function call */
 135+ function __call( $name, $args ) {
 136+ if ( is_null( $this->realObj ) ) {
 137+ $this->realObj = new ExtParserFunctions;
 138+ $this->realObj->clearState( $args[0] );
111139 }
 140+ return call_user_func_array( array( $this->realObj, $name ), $args );
112141 }
113 -
114 - function &getExprParser() {
115 - if ( !isset( $this->mExprParser ) ) {
116 - if ( !class_exists( 'ExprParser' ) ) {
117 - require( dirname( __FILE__ ) . '/Expr.php' );
118 - }
119 - $this->mExprParser = new ExprParser;
120 - }
121 - return $this->mExprParser;
122 - }
123 -
124 - function expr( &$parser, $expr = '' ) {
125 - try {
126 - return $this->getExprParser()->doExpression( $expr );
127 - } catch(ExprError $e) {
128 - return $e->getMessage();
129 - }
130 - }
131 -
132 - function ifexpr( &$parser, $expr = '', $then = '', $else = '' ) {
133 - try{
134 - if($this->getExprParser()->doExpression( $expr )) {
135 - return $then;
136 - } else {
137 - return $else;
138 - }
139 - } catch (ExprError $e){
140 - return $e->getMessage();
141 - }
142 - }
143 -
144 - function ifexprObj( $parser, $frame, $args ) {
145 - $expr = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
146 - $then = isset( $args[1] ) ? $args[1] : '';
147 - $else = isset( $args[2] ) ? $args[2] : '';
148 - $result = $this->ifexpr( $parser, $expr, $then, $else );
149 - if ( is_object( $result ) ) {
150 - $result = trim( $frame->expand( $result ) );
151 - }
152 - return $result;
153 - }
154 -
155 - function ifHook( &$parser, $test = '', $then = '', $else = '' ) {
156 - if ( $test !== '' ) {
157 - return $then;
158 - } else {
159 - return $else;
160 - }
161 - }
162 -
163 - function ifObj( &$parser, $frame, $args ) {
164 - $test = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
165 - if ( $test !== '' ) {
166 - return isset( $args[1] ) ? trim( $frame->expand( $args[1] ) ) : '';
167 - } else {
168 - return isset( $args[2] ) ? trim( $frame->expand( $args[2] ) ) : '';
169 - }
170 - }
171 -
172 - function ifeq( &$parser, $left = '', $right = '', $then = '', $else = '' ) {
173 - if ( $left == $right ) {
174 - return $then;
175 - } else {
176 - return $else;
177 - }
178 - }
179 -
180 - function ifeqObj( &$parser, $frame, $args ) {
181 - $left = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
182 - $right = isset( $args[1] ) ? trim( $frame->expand( $args[1] ) ) : '';
183 - if ( $left == $right ) {
184 - return isset( $args[2] ) ? trim( $frame->expand( $args[2] ) ) : '';
185 - } else {
186 - return isset( $args[3] ) ? trim( $frame->expand( $args[3] ) ) : '';
187 - }
188 - }
189 -
190 - function iferror( &$parser, $test = '', $then = '', $else = false ) {
191 - if ( preg_match( '/<(?:strong|span|p|div)\s(?:[^\s>]*\s+)*?class="(?:[^"\s>]*\s+)*?error(?:\s[^">]*)?"/', $test ) ) {
192 - return $then;
193 - } elseif ( $else === false ) {
194 - return $test;
195 - } else {
196 - return $else;
197 - }
198 - }
199 -
200 - function iferrorObj( &$parser, $frame, $args ) {
201 - $test = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
202 - $then = isset( $args[1] ) ? $args[1] : false;
203 - $else = isset( $args[2] ) ? $args[2] : false;
204 - $result = $this->iferror( $parser, $test, $then, $else );
205 - if ( $result === false ) {
206 - return '';
207 - } else {
208 - return trim( $frame->expand( $result ) );
209 - }
210 - }
211 -
212 - function switchHook( &$parser /*,...*/ ) {
213 - $args = func_get_args();
214 - array_shift( $args );
215 - $primary = trim(array_shift($args));
216 - $found = false;
217 - $parts = null;
218 - $default = null;
219 - $mwDefault =& MagicWord::get( 'default' );
220 - foreach( $args as $arg ) {
221 - $parts = array_map( 'trim', explode( '=', $arg, 2 ) );
222 - if ( count( $parts ) == 2 ) {
223 - # Found "="
224 - if ( $found || $parts[0] == $primary ) {
225 - # Found a match, return now
226 - return $parts[1];
227 - } else {
228 - if ( $mwDefault->matchStartAndRemove( $parts[0] ) ) {
229 - $default = $parts[1];
230 - } # else wrong case, continue
231 - }
232 - } elseif ( count( $parts ) == 1 ) {
233 - # Multiple input, single output
234 - # If the value matches, set a flag and continue
235 - if ( $parts[0] == $primary ) {
236 - $found = true;
237 - }
238 - } # else RAM corruption due to cosmic ray?
239 - }
240 - # Default case
241 - # Check if the last item had no = sign, thus specifying the default case
242 - if ( count( $parts ) == 1) {
243 - return $parts[0];
244 - } elseif ( !is_null( $default ) ) {
245 - return $default;
246 - } else {
247 - return '';
248 - }
249 - }
250 -
251 - function switchObj( $parser, $frame, $args ) {
252 - if ( count( $args ) == 0 ) {
253 - return '';
254 - }
255 - $primary = trim( $frame->expand( array_shift( $args ) ) );
256 - $found = false;
257 - $default = null;
258 - $lastItemHadNoEquals = false;
259 - $mwDefault =& MagicWord::get( 'default' );
260 - foreach ( $args as $arg ) {
261 - $bits = $arg->splitArg();
262 - $nameNode = $bits['name'];
263 - $index = $bits['index'];
264 - $valueNode = $bits['value'];
265 -
266 - if ( $index === '' ) {
267 - # Found "="
268 - $lastItemHadNoEquals = false;
269 - $test = trim( $frame->expand( $nameNode ) );
270 - if ( $found ) {
271 - # Multiple input match
272 - return trim( $frame->expand( $valueNode ) );
273 - } else {
274 - $test = trim( $frame->expand( $nameNode ) );
275 - if ( $test == $primary ) {
276 - # Found a match, return now
277 - return trim( $frame->expand( $valueNode ) );
278 - } else {
279 - if ( $mwDefault->matchStartAndRemove( $test ) ) {
280 - $default = $valueNode;
281 - } # else wrong case, continue
282 - }
283 - }
284 - } else {
285 - # Multiple input, single output
286 - # If the value matches, set a flag and continue
287 - $lastItemHadNoEquals = true;
288 - $test = trim( $frame->expand( $valueNode ) );
289 - if ( $test == $primary ) {
290 - $found = true;
291 - }
292 - }
293 - }
294 - # Default case
295 - # Check if the last item had no = sign, thus specifying the default case
296 - if ( $lastItemHadNoEquals ) {
297 - return $test;
298 - } elseif ( !is_null( $default ) ) {
299 - return trim( $frame->expand( $default ) );
300 - } else {
301 - return '';
302 - }
303 - }
304 -
305 - /**
306 - * Returns the absolute path to a subpage, relative to the current article
307 - * title. Treats titles as slash-separated paths.
308 - *
309 - * Following subpage link syntax instead of standard path syntax, an
310 - * initial slash is treated as a relative path, and vice versa.
311 - */
312 - public function rel2abs( &$parser , $to = '' , $from = '' ) {
313 -
314 - $from = trim($from);
315 - if( $from == '' ) {
316 - $from = $parser->getTitle()->getPrefixedText();
317 - }
318 -
319 - $to = rtrim( $to , ' /' );
320 -
321 - // if we have an empty path, or just one containing a dot
322 - if( $to == '' || $to == '.' ) {
323 - return $from;
324 - }
325 -
326 - // if the path isn't relative
327 - if ( substr( $to , 0 , 1) != '/' &&
328 - substr( $to , 0 , 2) != './' &&
329 - substr( $to , 0 , 3) != '../' &&
330 - $to != '..' )
331 - {
332 - $from = '';
333 - }
334 - // Make a long path, containing both, enclose it in /.../
335 - $fullPath = '/' . $from . '/' . $to . '/';
336 -
337 - // remove redundant current path dots
338 - $fullPath = preg_replace( '!/(\./)+!', '/', $fullPath );
339 -
340 - // remove double slashes
341 - $fullPath = preg_replace( '!/{2,}!', '/', $fullPath );
342 -
343 - // remove the enclosing slashes now
344 - $fullPath = trim( $fullPath , '/' );
345 - $exploded = explode ( '/' , $fullPath );
346 - $newExploded = array();
347 -
348 - foreach ( $exploded as $current ) {
349 - if( $current == '..' ) { // removing one level
350 - if( !count( $newExploded ) ){
351 - // attempted to access a node above root node
352 - wfLoadExtensionMessages( 'ParserFunctions' );
353 - return '<strong class="error">' . wfMsgForContent( 'pfunc_rel2abs_invalid_depth', $fullPath ) . '</strong>';
354 - }
355 - // remove last level from the stack
356 - array_pop( $newExploded );
357 - } else {
358 - // add the current level to the stack
359 - $newExploded[] = $current;
360 - }
361 - }
362 -
363 - // we can now join it again
364 - return implode( '/' , $newExploded );
365 - }
366 -
367 - function incrementIfexistCount( $parser, $frame ) {
368 - // Don't let this be called more than a certain number of times. It tends to make the database explode.
369 - global $wgExpensiveParserFunctionLimit;
370 - $parser->mExpensiveFunctionCount++;
371 - if ( $frame ) {
372 - $pdbk = $frame->getPDBK( 1 );
373 - if ( !isset( $parser->pf_ifexist_breakdown[$pdbk] ) ) {
374 - $parser->pf_ifexist_breakdown[$pdbk] = 0;
375 - }
376 - $parser->pf_ifexist_breakdown[$pdbk] ++;
377 - }
378 - return $parser->mExpensiveFunctionCount <= $wgExpensiveParserFunctionLimit;
379 - }
380 -
381 - function ifexist( &$parser, $title = '', $then = '', $else = '' ) {
382 - return $this->ifexistCommon( $parser, false, $title, $then, $else );
383 - }
384 -
385 - function ifexistCommon( &$parser, $frame, $titletext = '', $then = '', $else = '' ) {
386 - global $wgContLang;
387 - $title = Title::newFromText( $titletext );
388 - $wgContLang->findVariantLink( $titletext, $title, true );
389 - if ( $title ) {
390 - if( $title->getNamespace() == NS_MEDIA ) {
391 - /* If namespace is specified as NS_MEDIA, then we want to
392 - * check the physical file, not the "description" page.
393 - */
394 - if ( !$this->incrementIfexistCount( $parser, $frame ) ) {
395 - return $else;
396 - }
397 - $file = wfFindFile($title);
398 - if ( !$file ) {
399 - return $else;
400 - }
401 - $parser->mOutput->addImage($file->getName());
402 - return $file->exists() ? $then : $else;
403 - } elseif( $title->getNamespace() == NS_SPECIAL ) {
404 - /* Don't bother with the count for special pages,
405 - * since their existence can be checked without
406 - * accessing the database.
407 - */
408 - return SpecialPage::exists( $title->getDBkey() ) ? $then : $else;
409 - } elseif( $title->isExternal() ) {
410 - /* Can't check the existence of pages on other sites,
411 - * so just return $else. Makes a sort of sense, since
412 - * they don't exist _locally_.
413 - */
414 - return $else;
415 - } else {
416 - $pdbk = $title->getPrefixedDBkey();
417 - $lc = LinkCache::singleton();
418 - if ( !$this->incrementIfexistCount( $parser, $frame ) ) {
419 - return $else;
420 - }
421 - if ( 0 != ( $id = $lc->getGoodLinkID( $pdbk ) ) ) {
422 - $parser->mOutput->addLink( $title, $id );
423 - return $then;
424 - } elseif ( $lc->isBadLink( $pdbk ) ) {
425 - $parser->mOutput->addLink( $title, 0 );
426 - return $else;
427 - }
428 - $id = $title->getArticleID();
429 - $parser->mOutput->addLink( $title, $id );
430 - if ( $id ) {
431 - return $then;
432 - }
433 - }
434 - }
435 - return $else;
436 - }
437 -
438 - function ifexistObj( &$parser, $frame, $args ) {
439 - $title = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
440 - $then = isset( $args[1] ) ? $args[1] : null;
441 - $else = isset( $args[2] ) ? $args[2] : null;
442 -
443 - $result = $this->ifexistCommon( $parser, $frame, $title, $then, $else );
444 - if ( $result === null ) {
445 - return '';
446 - } else {
447 - return trim( $frame->expand( $result ) );
448 - }
449 - }
450 -
451 - function time( &$parser, $format = '', $date = '', $local = false ) {
452 - global $wgContLang, $wgLocaltimezone;
453 - if ( isset( $this->mTimeCache[$format][$date][$local] ) ) {
454 - return $this->mTimeCache[$format][$date][$local];
455 - }
456 -
457 - #compute the timestamp string $ts
458 - #PHP >= 5.2 can handle dates before 1970 or after 2038 using the DateTime object
459 - #PHP < 5.2 is limited to dates between 1970 and 2038
460 -
461 - $invalidTime = false;
462 -
463 - if ( class_exists( 'DateTime' ) ) { #PHP >= 5.2
464 - # the DateTime constructor must be used because it throws exceptions
465 - # when errors occur, whereas date_create appears to just output a warning
466 - # that can't really be detected from within the code
467 - try {
468 - # Determine timezone
469 - if ( $local ) {
470 - # convert to MediaWiki local timezone if set
471 - if ( isset( $wgLocaltimezone ) ) {
472 - $tz = new DateTimeZone( $wgLocaltimezone );
473 - } else {
474 - $tz = new DateTimeZone( 'UTC' );
475 - }
476 - } else {
477 - # if local time was not requested, convert to UTC
478 - $tz = new DateTimeZone( 'UTC' );
479 - }
480 -
481 - # Parse date
482 - if ( $date !== '' ) {
483 - $dateObject = new DateTime( $date, $tz );
484 - } else {
485 - # use current date and time
486 - $dateObject = new DateTime( 'now', $tz );
487 - }
488 -
489 - # Generate timestamp
490 - $ts = $dateObject->format( 'YmdHis' );
491 - } catch (Exception $ex) {
492 - $invalidTime = true;
493 - }
494 - } else { #PHP < 5.2
495 - if ( $date !== '' ) {
496 - $unix = @strtotime( $date );
497 - } else {
498 - $unix = time();
499 - }
500 -
501 - if ( $unix == -1 || $unix == false ) {
502 - $invalidTime = true;
503 - } else {
504 - if ( $local ) {
505 - # Use the time zone
506 - if ( isset( $wgLocaltimezone ) ) {
507 - $oldtz = getenv( 'TZ' );
508 - putenv( 'TZ='.$wgLocaltimezone );
509 - }
510 - wfSuppressWarnings(); // E_STRICT system time bitching
511 - $ts = date( 'YmdHis', $unix );
512 - wfRestoreWarnings();
513 - if ( isset( $wgLocaltimezone ) ) {
514 - putenv( 'TZ='.$oldtz );
515 - }
516 - } else {
517 - $ts = wfTimestamp( TS_MW, $unix );
518 - }
519 - }
520 - }
521 -
522 - #format the timestamp and return the result
523 - if ( $invalidTime ) {
524 - wfLoadExtensionMessages( 'ParserFunctions' );
525 - $result = '<strong class="error">' . wfMsgForContent( 'pfunc_time_error' ) . '</strong>';
526 - } else {
527 - $this->mTimeChars += strlen( $format );
528 - if ( $this->mTimeChars > $this->mMaxTimeChars ) {
529 - wfLoadExtensionMessages( 'ParserFunctions' );
530 - return '<strong class="error">' . wfMsgForContent( 'pfunc_time_too_long' ) . '</strong>';
531 - } else {
532 -
533 - if ( method_exists( $wgContLang, 'sprintfDate' ) ) {
534 - $result = $wgContLang->sprintfDate( $format, $ts );
535 - } else {
536 - if ( !class_exists( 'SprintfDateCompat' ) ) {
537 - require( dirname( __FILE__ ) . '/SprintfDateCompat.php' );
538 - }
539 -
540 - $result = SprintfDateCompat::sprintfDate( $format, $ts );
541 - }
542 - }
543 - }
544 - $this->mTimeCache[$format][$date][$local] = $result;
545 - return $result;
546 - }
547 -
548 - function localTime( &$parser, $format = '', $date = '' ) {
549 - return $this->time( $parser, $format, $date, true );
550 - }
551 -
552 - /**
553 - * Obtain a specified number of slash-separated parts of a title,
554 - * e.g. {{#titleparts:Hello/World|1}} => "Hello"
555 - *
556 - * @param Parser $parser Parent parser
557 - * @param string $title Title to split
558 - * @param int $parts Number of parts to keep
559 - * @param int $offset Offset starting at 1
560 - * @return string
561 - */
562 - public function titleparts( $parser, $title = '', $parts = 0, $offset = 0) {
563 - $parts = intval( $parts );
564 - $offset = intval( $offset );
565 - $ntitle = Title::newFromText( $title );
566 - if ( $ntitle instanceof Title ) {
567 - $bits = explode( '/', $ntitle->getPrefixedText(), 25 );
568 - if ( count( $bits ) <= 0 ) {
569 - return $ntitle->getPrefixedText();
570 - } else {
571 - if ( $offset > 0 ) {
572 - --$offset;
573 - }
574 - if ( $parts == 0 ) {
575 - return implode( '/', array_slice( $bits, $offset ) );
576 - } else {
577 - return implode( '/', array_slice( $bits, $offset, $parts ) );
578 - }
579 - }
580 - } else {
581 - return $title;
582 - }
583 - }
584 -
585 - // Verifies parameter is less than max string length.
586 - private function checkLength( $text ) {
587 - global $wgStringFunctionsLimit;
588 - return ( mb_strlen( $text ) < $wgStringFunctionsLimit );
589 - }
590 -
591 - // Generates error message. Called when string is too long.
592 - private function tooLongError() {
593 - global $wgStringFunctionsLimit, $wgContLang;
594 - wfLoadExtensionMessages( 'ParserFunctions' );
595 -
596 - return '<strong class="error">' .
597 - wfMsgExt( 'pfunc_string_too_long',
598 - array( 'escape', 'parsemag', 'content' ),
599 - $wgContLang->formatNum( $wgStringFunctionsLimit ) ) .
600 - '</strong>';
601 - }
602 -
603 - /**
604 - * {{#len:string}}
605 - *
606 - * Reports number of characters in string.
607 - */
608 - function runLen ( &$parser, $inStr = '' ) {
609 - wfProfileIn( __METHOD__ );
610 -
611 - $inStr = $this->killMarkers( (string)$inStr );
612 - $len = mb_strlen( $inStr );
613 -
614 - wfProfileOut( __METHOD__ );
615 - return $len;
616 - }
617 -
618 - /**
619 - * {{#pos: string | needle | offset}}
620 - *
621 - * Finds first occurrence of "needle" in "string" starting at "offset".
622 - *
623 - * Note: If the needle is an empty string, single space is used instead.
624 - * Note: If the needle is not found, empty string is returned.
625 - */
626 - function runPos ( &$parser, $inStr = '', $inNeedle = '', $inOffset = 0 ) {
627 - wfProfileIn( __METHOD__ );
628 -
629 - $inStr = $this->killMarkers( (string)$inStr );
630 - $inNeedle = $this->killMarkers( (string)$inNeedle );
631 -
632 - if( !$this->checkLength( $inStr ) ||
633 - !$this->checkLength( $inNeedle ) ) {
634 - wfProfileOut( __METHOD__ );
635 - return $this->tooLongError();
636 - }
637 -
638 - if( $inNeedle == '' ) { $inNeedle = ' '; }
639 -
640 - $pos = mb_strpos( $inStr, $inNeedle, $inOffset );
641 - if( $pos === false ) { $pos = ""; }
642 -
643 - wfProfileOut( __METHOD__ );
644 - return $pos;
645 - }
646 -
647 - /**
648 - * {{#rpos: string | needle}}
649 - *
650 - * Finds last occurrence of "needle" in "string".
651 - *
652 - * Note: If the needle is an empty string, single space is used instead.
653 - * Note: If the needle is not found, -1 is returned.
654 - */
655 - function runRPos ( &$parser, $inStr = '', $inNeedle = '' ) {
656 - wfProfileIn( __METHOD__ );
657 -
658 - $inStr = $this->killMarkers( (string)$inStr );
659 - $inNeedle = $this->killMarkers( (string)$inNeedle );
660 -
661 - if( !$this->checkLength( $inStr ) ||
662 - !$this->checkLength( $inNeedle ) ) {
663 - wfProfileOut( __METHOD__ );
664 - return $this->tooLongError();
665 - }
666 -
667 - if( $inNeedle == '' ) { $inNeedle = ' '; }
668 -
669 - $pos = mb_strrpos( $inStr, $inNeedle );
670 - if( $pos === false ) { $pos = -1; }
671 -
672 - wfProfileOut( __METHOD__ );
673 - return $pos;
674 - }
675 -
676 - /**
677 - * {{#sub: string | start | length }}
678 - *
679 - * Returns substring of "string" starting at "start" and having
680 - * "length" characters.
681 - *
682 - * Note: If length is zero, the rest of the input is returned.
683 - * Note: A negative value for "start" operates from the end of the
684 - * "string".
685 - * Note: A negative value for "length" returns a string reduced in
686 - * length by that amount.
687 - */
688 - function runSub ( &$parser, $inStr = '', $inStart = 0, $inLength = 0 ) {
689 - wfProfileIn( __METHOD__ );
690 -
691 - $inStr = $this->killMarkers( (string)$inStr );
692 -
693 - if( !$this->checkLength( $inStr ) ) {
694 - wfProfileOut( __METHOD__ );
695 - return $this->tooLongError();
696 - }
697 -
698 - if ( intval($inLength) == 0 ) {
699 - $result = mb_substr( $inStr, $inStart );
700 - } else {
701 - $result = mb_substr( $inStr, $inStart, $inLength );
702 - }
703 -
704 - wfProfileOut( __METHOD__ );
705 - return $result;
706 - }
707 -
708 - /**
709 - * {{#count: string | substr }}
710 - *
711 - * Returns number of occurrences of "substr" in "string".
712 - *
713 - * Note: If "substr" is empty, a single space is used.
714 - */
715 - function runCount ( &$parser, $inStr = '', $inSubStr = '' ) {
716 - wfProfileIn( __METHOD__ );
717 -
718 - $inStr = $this->killMarkers( (string)$inStr );
719 - $inSubStr = $this->killMarkers( (string)$inSubStr );
720 -
721 - if( !$this->checkLength( $inStr ) ||
722 - !$this->checkLength( $inSubStr ) ) {
723 - wfProfileOut( __METHOD__ );
724 - return $this->tooLongError();
725 - }
726 -
727 - if( $inSubStr == '' ) { $inSubStr = ' '; }
728 -
729 - $result = mb_substr_count( $inStr, $inSubStr );
730 -
731 - wfProfileOut( __METHOD__ );
732 - return $result;
733 - }
734 -
735 - /**
736 - * {{#replace:string | from | to | limit }}
737 - *
738 - * Replaces each occurrence of "from" in "string" with "to".
739 - * At most "limit" replacements are performed.
740 - *
741 - * Note: Armored against replacements that would generate huge strings.
742 - * Note: If "from" is an empty string, single space is used instead.
743 - */
744 - function runReplace( &$parser, $inStr = '',
745 - $inReplaceFrom = '', $inReplaceTo = '', $inLimit = -1 ) {
746 - global $wgStringFunctionsLimit;
747 - wfProfileIn( __METHOD__ );
748 -
749 - $inStr = $this->killMarkers( (string)$inStr );
750 - $inReplaceFrom = $this->killMarkers( (string)$inReplaceFrom );
751 - $inReplaceTo = $this->killMarkers( (string)$inReplaceTo );
752 -
753 - if( !$this->checkLength( $inStr ) ||
754 - !$this->checkLength( $inReplaceFrom ) ||
755 - !$this->checkLength( $inReplaceTo ) ) {
756 - wfProfileOut( __METHOD__ );
757 - return $this->tooLongError();
758 - }
759 -
760 - if( $inReplaceFrom == '' ) { $inReplaceFrom = ' '; }
761 -
762 - // Precompute limit to avoid generating enormous string:
763 - $diff = mb_strlen( $inReplaceTo ) - mb_strlen( $inReplaceFrom );
764 - if( $diff > 0 ) {
765 - $limit = ( ( $wgStringFunctionsLimit - mb_strlen( $inStr ) ) / $diff ) + 1;
766 - } else {
767 - $limit = -1;
768 - }
769 -
770 - $inLimit = intval($inLimit);
771 - if( $inLimit >= 0 ) {
772 - if( $limit > $inLimit || $limit == -1 ) { $limit = $inLimit; }
773 - }
774 -
775 - // Use regex to allow limit and handle UTF-8 correctly.
776 - $inReplaceFrom = preg_quote( $inReplaceFrom, '/' );
777 - $inReplaceTo = preg_quote( $inReplaceTo, '/' );
778 -
779 - $result = preg_replace( '/' . $inReplaceFrom . '/u',
780 - $inReplaceTo, $inStr, $limit);
781 -
782 - if( !$this->checkLength( $result ) ) {
783 - wfProfileOut( __METHOD__ );
784 - return $this->tooLongError();
785 - }
786 -
787 - wfProfileOut( __METHOD__ );
788 - return $result;
789 - }
790 -
791 -
792 - /**
793 - * {{#explode:string | delimiter | position}}
794 - *
795 - * Breaks "string" into chunks separated by "delimiter" and returns the
796 - * chunk identified by "position".
797 - *
798 - * Note: Negative position can be used to specify tokens from the end.
799 - * Note: If the divider is an empty string, single space is used instead.
800 - * Note: Empty string is returned if there are not enough exploded chunks.
801 - */
802 - function runExplode ( &$parser, $inStr = '', $inDiv = '', $inPos = 0 ) {
803 - wfProfileIn( __METHOD__ );
804 -
805 - $inStr = $this->killMarkers( (string)$inStr );
806 - $inDiv = $this->killMarkers( (string)$inDiv );
807 -
808 - if( $inDiv == '' ) { $inDiv = ' '; }
809 -
810 - if( !$this->checkLength( $inStr ) ||
811 - !$this->checkLength( $inDiv ) ) {
812 - wfProfileOut( __METHOD__ );
813 - return $this->tooLongError();
814 - }
815 -
816 - $inStr = preg_quote( $inStr, '/' );
817 - $inDiv = preg_quote( $inDiv, '/' );
818 -
819 - $matches = preg_split( '/'.$inDiv.'/u', $inStr );
820 -
821 - if( $inPos >= 0 && isset( $matches[$inPos] ) ) {
822 - $result = $matches[$inPos];
823 - } elseif ( $inPos < 0 && isset( $matches[count($matches) + $inPos] ) ) {
824 - $result = $matches[count($matches) + $inPos];
825 - } else {
826 - $result = '';
827 - }
828 -
829 - wfProfileOut( __METHOD__ );
830 - return $result;
831 - }
832142 }
833 -
834 -function wfSetupParserFunctions() {
835 - global $wgParser, $wgExtParserFunctions, $wgHooks;
836 -
837 - $wgExtParserFunctions = new ExtParserFunctions;
838 -
839 - // Check for SFH_OBJECT_ARGS capability
840 - if ( defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' ) ) {
841 - $wgHooks['ParserFirstCallInit'][] = array( &$wgExtParserFunctions, 'registerParser' );
842 - } else {
843 - if ( class_exists( 'StubObject' ) && !StubObject::isRealObject( $wgParser ) ) {
844 - $wgParser->_unstub();
845 - }
846 - $wgExtParserFunctions->registerParser( $wgParser );
847 - }
848 -
849 - $wgHooks['ParserClearState'][] = array( &$wgExtParserFunctions, 'clearState' );
850 -}
851 -
852 -function wfParserFunctionsLanguageGetMagic( &$magicWords, $langCode ) {
853 - require_once( dirname( __FILE__ ) . '/ParserFunctions.i18n.magic.php' );
854 - foreach( efParserFunctionsWords( $langCode ) as $word => $trans )
855 - $magicWords[$word] = $trans;
856 - return true;
857 -}
858 -

Comments

#Comment by Happy-melon (talk | contribs)   15:22, 5 June 2009

That's got to go into the Bugzilla quips list... :D

Status & tagging log