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

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy