r51497 - Code Review

From MediaWiki.org

Jump to: navigation, search
Repository:MediaWiki
Revision:r51496 | r51497 (on ViewVC) | 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:

Diff [purge]

Index: trunk/extensions/ParserFunctions/ParserFunctions_body.php
===================================================================
--- trunk/extensions/ParserFunctions/ParserFunctions_body.php	(revision 0)
+++ trunk/extensions/ParserFunctions/ParserFunctions_body.php	(revision 51497)
@@ -0,0 +1,769 @@
+<?php
+
+class ExtParserFunctions {
+	var $mExprParser;
+	var $mTimeCache = array();
+	var $mTimeChars = 0;
+	var $mMaxTimeChars = 6000; # ~10 seconds
+
+	function clearState(&$parser) {
+		$this->mTimeChars = 0;
+		$parser->pf_ifexist_breakdown = array();
+		$parser->pf_markerRegex = null;
+		return true;
+	}
+
+	/**
+	 * Get the marker regex. Cached.
+	 */
+	function getMarkerRegex( $parser ) {
+		if ( isset( $parser->pf_markerRegex ) ) {
+			return $parser->pf_markerRegex;
+		}
+
+		wfProfileIn( __METHOD__ );
+
+		$prefix = preg_quote( $parser->uniqPrefix(), '/' );
+
+		// The first line represents Parser from release 1.12 forward.
+		// subsequent lines are hacks to accomodate old Mediawiki versions.
+		if ( defined('Parser::MARKER_SUFFIX') )
+			$suffix = preg_quote( Parser::MARKER_SUFFIX, '/' );
+		elseif ( isset($parser->mMarkerSuffix) )
+			$suffix = preg_quote( $parser->mMarkerSuffix, '/' );
+		elseif ( defined('MW_PARSER_VERSION') && 
+				strcmp( MW_PARSER_VERSION, '1.6.1' ) > 0 )
+			$suffix = "QINU\x07";
+		else $suffix = 'QINU';
+		
+		$parser->pf_markerRegex = '/' .$prefix. '(?:(?!' .$suffix. ').)*' . $suffix . '/us';
+
+		wfProfileOut( __METHOD__ );
+		return $parser->pf_markerRegex;
+	}
+
+	// Removes unique markers from passed parameters, used by string functions.
+	private function killMarkers ( $parser, $text ) {
+		return preg_replace( $this->getMarkerRegex( $parser ), '' , $text );
+	}
+
+	function &getExprParser() {
+		if ( !isset( $this->mExprParser ) ) {
+			if ( !class_exists( 'ExprParser' ) ) {
+				require( dirname( __FILE__ ) . '/Expr.php' );
+			}
+			$this->mExprParser = new ExprParser;
+		}
+		return $this->mExprParser;
+	}
+
+	function expr( &$parser, $expr = '' ) {
+		try {
+			return $this->getExprParser()->doExpression( $expr );
+		} catch(ExprError $e) {
+			return $e->getMessage();
+		}
+	}
+
+	function ifexpr( &$parser, $expr = '', $then = '', $else = '' ) {
+		try{
+			if($this->getExprParser()->doExpression( $expr )) {
+				return $then;
+			} else {
+				return $else;
+			}
+		} catch (ExprError $e){
+			return $e->getMessage();
+		}
+	}
+
+	function ifexprObj( $parser, $frame, $args ) {
+		$expr = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
+		$then = isset( $args[1] ) ? $args[1] : '';
+		$else = isset( $args[2] ) ? $args[2] : '';
+		$result = $this->ifexpr( $parser, $expr, $then, $else );
+		if ( is_object( $result ) ) {
+			$result = trim( $frame->expand( $result ) );
+		}
+		return $result;
+	}
+
+	function ifHook( &$parser, $test = '', $then = '', $else = '' ) {
+		if ( $test !== '' ) {
+			return $then;
+		} else {
+			return $else;
+		}
+	}
+
+	function ifObj( &$parser, $frame, $args ) {
+		$test = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
+		if ( $test !== '' ) {
+			return isset( $args[1] ) ? trim( $frame->expand( $args[1] ) ) : '';
+		} else {
+			return isset( $args[2] ) ? trim( $frame->expand( $args[2] ) ) : '';
+		}
+	}
+
+	function ifeq( &$parser, $left = '', $right = '', $then = '', $else = '' ) {
+		if ( $left == $right ) {
+			return $then;
+		} else {
+			return $else;
+		}
+	}
+
+	function ifeqObj( &$parser, $frame, $args ) {
+		$left = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
+		$right = isset( $args[1] ) ? trim( $frame->expand( $args[1] ) ) : '';
+		if ( $left == $right ) {
+			return isset( $args[2] ) ? trim( $frame->expand( $args[2] ) ) : '';
+		} else {
+			return isset( $args[3] ) ? trim( $frame->expand( $args[3] ) ) : '';
+		}
+	}
+
+	function iferror( &$parser, $test = '', $then = '', $else = false ) {
+		if ( preg_match( '/<(?:strong|span|p|div)\s(?:[^\s>]*\s+)*?class="(?:[^"\s>]*\s+)*?error(?:\s[^">]*)?"/', $test ) ) {
+			return $then;
+		} elseif ( $else === false ) {
+			return $test;
+		} else {
+			return $else;
+		}
+	}
+
+	function iferrorObj( &$parser, $frame, $args ) {
+		$test = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
+		$then = isset( $args[1] ) ? $args[1] : false;
+		$else = isset( $args[2] ) ? $args[2] : false;
+		$result = $this->iferror( $parser, $test, $then, $else );
+		if ( $result === false ) {
+			return '';
+		} else {
+			return trim( $frame->expand( $result ) );
+		}
+	}
+
+	function switchHook( &$parser /*,...*/ ) {
+		$args = func_get_args();
+		array_shift( $args );
+		$primary = trim(array_shift($args));
+		$found = false;
+		$parts = null;
+		$default = null;
+		$mwDefault =& MagicWord::get( 'default' );
+		foreach( $args as $arg ) {
+			$parts = array_map( 'trim', explode( '=', $arg, 2 ) );
+			if ( count( $parts ) == 2 ) {
+				# Found "="
+				if ( $found || $parts[0] == $primary ) {
+					# Found a match, return now
+					return $parts[1];
+				} else {
+					if ( $mwDefault->matchStartAndRemove( $parts[0] ) ) {
+						$default = $parts[1];
+					} # else wrong case, continue
+				}
+			} elseif ( count( $parts ) == 1 ) {
+				# Multiple input, single output
+				# If the value matches, set a flag and continue
+				if ( $parts[0] == $primary ) {
+					$found = true;
+				}
+			} # else RAM corruption due to cosmic ray?
+		}
+		# Default case
+		# Check if the last item had no = sign, thus specifying the default case
+		if ( count( $parts ) == 1) {
+			return $parts[0];
+		} elseif ( !is_null( $default ) ) {
+			return $default;
+		} else {
+			return '';
+		}
+	}
+
+	function switchObj( $parser, $frame, $args ) {
+		if ( count( $args ) == 0 ) {
+			return '';
+		}
+		$primary = trim( $frame->expand( array_shift( $args ) ) );
+		$found = false;
+		$default = null;
+		$lastItemHadNoEquals = false;
+		$mwDefault =& MagicWord::get( 'default' );
+		foreach ( $args as $arg ) {
+			$bits = $arg->splitArg();
+			$nameNode = $bits['name'];
+			$index = $bits['index'];
+			$valueNode = $bits['value'];
+
+			if ( $index === '' ) {
+				# Found "="
+				$lastItemHadNoEquals = false;
+				$test = trim( $frame->expand( $nameNode ) );
+				if ( $found ) {
+					# Multiple input match
+					return trim( $frame->expand( $valueNode ) );
+				} else {
+					$test = trim( $frame->expand( $nameNode ) );
+					if ( $test == $primary ) {
+						# Found a match, return now
+						return trim( $frame->expand( $valueNode ) );
+					} else {
+						if ( $mwDefault->matchStartAndRemove( $test ) ) {
+							$default = $valueNode;
+						} # else wrong case, continue
+					}
+				}
+			} else {
+				# Multiple input, single output
+				# If the value matches, set a flag and continue
+				$lastItemHadNoEquals = true;
+				$test = trim( $frame->expand( $valueNode ) );
+				if ( $test == $primary ) {
+					$found = true;
+				}
+			}
+		}
+		# Default case
+		# Check if the last item had no = sign, thus specifying the default case
+		if ( $lastItemHadNoEquals ) {
+			return $test;
+		} elseif ( !is_null( $default ) ) {
+			return trim( $frame->expand( $default ) );
+		} else {
+			return '';
+		}
+	}
+
+	/**
+	 * Returns the absolute path to a subpage, relative to the current article
+	 * title. Treats titles as slash-separated paths.
+	 *
+	 * Following subpage link syntax instead of standard path syntax, an
+	 * initial slash is treated as a relative path, and vice versa.
+	 */
+	public function rel2abs( &$parser , $to = '' , $from = '' ) {
+
+		$from = trim($from);
+		if( $from == '' ) {
+			$from = $parser->getTitle()->getPrefixedText();
+		}
+
+		$to = rtrim( $to , ' /' );
+
+		// if we have an empty path, or just one containing a dot
+		if( $to == '' || $to == '.' ) {
+			return $from;
+		}
+
+		// if the path isn't relative
+		if ( substr( $to , 0 , 1) != '/' &&
+		 substr( $to , 0 , 2) != './' &&
+		 substr( $to , 0 , 3) != '../' &&
+		 $to != '..' )
+		{
+			$from = '';
+		}
+		// Make a long path, containing both, enclose it in /.../
+		$fullPath = '/' . $from . '/' .  $to . '/';
+
+		// remove redundant current path dots
+		$fullPath = preg_replace( '!/(\./)+!', '/', $fullPath );
+
+		// remove double slashes
+		$fullPath = preg_replace( '!/{2,}!', '/', $fullPath );
+
+		// remove the enclosing slashes now
+		$fullPath = trim( $fullPath , '/' );
+		$exploded = explode ( '/' , $fullPath );
+		$newExploded = array();
+
+		foreach ( $exploded as $current ) {
+			if( $current == '..' ) { // removing one level
+				if( !count( $newExploded ) ){
+					// attempted to access a node above root node
+					wfLoadExtensionMessages( 'ParserFunctions' );
+					return '<strong class="error">' . wfMsgForContent( 'pfunc_rel2abs_invalid_depth', $fullPath ) . '</strong>';
+				}
+				// remove last level from the stack
+				array_pop( $newExploded );
+			} else {
+				// add the current level to the stack
+				$newExploded[] = $current;
+			}
+		}
+
+		// we can now join it again
+		return implode( '/' , $newExploded );
+	}
+
+	function incrementIfexistCount( $parser, $frame ) {
+		// Don't let this be called more than a certain number of times. It tends to make the database explode.
+		global $wgExpensiveParserFunctionLimit;
+		$parser->mExpensiveFunctionCount++;
+		if ( $frame ) {
+			$pdbk = $frame->getPDBK( 1 );
+			if ( !isset( $parser->pf_ifexist_breakdown[$pdbk] ) ) {
+				$parser->pf_ifexist_breakdown[$pdbk] = 0;
+			}
+			$parser->pf_ifexist_breakdown[$pdbk] ++;
+		}
+		return $parser->mExpensiveFunctionCount <= $wgExpensiveParserFunctionLimit;
+	}
+
+	function ifexist( &$parser, $title = '', $then = '', $else = '' ) {
+		return $this->ifexistCommon( $parser, false, $title, $then, $else );
+	}
+
+	function ifexistCommon( &$parser, $frame, $titletext = '', $then = '', $else = '' ) {
+		global $wgContLang;
+		$title = Title::newFromText( $titletext );
+		$wgContLang->findVariantLink( $titletext, $title, true );
+		if ( $title ) {
+			if( $title->getNamespace() == NS_MEDIA ) {
+				/* If namespace is specified as NS_MEDIA, then we want to
+				 * check the physical file, not the "description" page.
+				 */
+				if ( !$this->incrementIfexistCount( $parser, $frame ) ) {
+					return $else;
+				}
+				$file = wfFindFile($title);
+				if ( !$file ) {
+					return $else;
+				}
+				$parser->mOutput->addImage($file->getName());
+				return $file->exists() ? $then : $else;
+			} elseif( $title->getNamespace() == NS_SPECIAL ) {
+				/* Don't bother with the count for special pages,
+				 * since their existence can be checked without
+				 * accessing the database.
+				 */
+				return SpecialPage::exists( $title->getDBkey() ) ? $then : $else;
+			} elseif( $title->isExternal() ) {
+				/* Can't check the existence of pages on other sites,
+				 * so just return $else.  Makes a sort of sense, since
+				 * they don't exist _locally_.
+				 */
+				return $else;
+			} else {
+				$pdbk = $title->getPrefixedDBkey();
+				$lc = LinkCache::singleton();
+				if ( !$this->incrementIfexistCount( $parser, $frame ) ) {
+					return $else;
+				}
+				if ( 0 != ( $id = $lc->getGoodLinkID( $pdbk ) ) ) {
+					$parser->mOutput->addLink( $title, $id );
+					return $then;
+				} elseif ( $lc->isBadLink( $pdbk ) ) {
+					$parser->mOutput->addLink( $title, 0 );
+					return $else;
+				}
+				$id = $title->getArticleID();
+				$parser->mOutput->addLink( $title, $id );
+				if ( $id ) {
+					return $then;
+				}
+			}
+		}
+		return $else;
+	}
+
+	function ifexistObj( &$parser, $frame, $args ) {
+		$title = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
+		$then = isset( $args[1] ) ? $args[1] : null;
+		$else = isset( $args[2] ) ? $args[2] : null;
+
+		$result = $this->ifexistCommon( $parser, $frame, $title, $then, $else );
+		if ( $result === null ) {
+			return '';
+		} else {
+			return trim( $frame->expand( $result ) );
+		}
+	}
+
+	function time( &$parser, $format = '', $date = '', $local = false ) {
+		global $wgContLang, $wgLocaltimezone;
+		if ( isset( $this->mTimeCache[$format][$date][$local] ) ) {
+			return $this->mTimeCache[$format][$date][$local];
+		}
+		
+		#compute the timestamp string $ts
+		#PHP >= 5.2 can handle dates before 1970 or after 2038 using the DateTime object
+		#PHP < 5.2 is limited to dates between 1970 and 2038
+		
+		$invalidTime = false;
+		
+		if ( class_exists( 'DateTime' ) ) { #PHP >= 5.2
+			# the DateTime constructor must be used because it throws exceptions 
+			# when errors occur, whereas date_create appears to just output a warning 
+			# that can't really be detected from within the code
+			try {
+				# Determine timezone
+				if ( $local ) {
+					 # convert to MediaWiki local timezone if set
+					if ( isset( $wgLocaltimezone ) ) {
+						$tz = new DateTimeZone( $wgLocaltimezone );
+					} else {
+						$tz = new DateTimeZone( 'UTC' );
+					}
+				} else {
+					# if local time was not requested, convert to UTC
+					$tz = new DateTimeZone( 'UTC' );
+				}
+
+				# Parse date
+				if ( $date !== '' ) {
+					$dateObject = new DateTime( $date, $tz );
+				} else {
+					# use current date and time
+					$dateObject = new DateTime( 'now', $tz );
+				}
+
+				# Generate timestamp
+				$ts = $dateObject->format( 'YmdHis' );
+			} catch (Exception $ex) {
+				$invalidTime = true;
+			}
+		} else { #PHP < 5.2
+			if ( $date !== '' ) {
+				$unix = @strtotime( $date );
+			} else {
+				$unix = time();
+			}
+			
+			if ( $unix == -1 || $unix == false ) {
+				$invalidTime = true;
+			} else {
+				if ( $local ) {
+					# Use the time zone
+					if ( isset( $wgLocaltimezone ) ) {
+						$oldtz = getenv( 'TZ' );
+						putenv( 'TZ='.$wgLocaltimezone );
+					}
+					wfSuppressWarnings(); // E_STRICT system time bitching
+					$ts = date( 'YmdHis', $unix );
+					wfRestoreWarnings();
+					if ( isset( $wgLocaltimezone ) ) {
+						putenv( 'TZ='.$oldtz );
+					}
+				} else {
+					$ts = wfTimestamp( TS_MW, $unix );
+				}
+			}
+		}
+		
+		#format the timestamp and return the result
+		if ( $invalidTime ) {
+			wfLoadExtensionMessages( 'ParserFunctions' );
+			$result = '<strong class="error">' . wfMsgForContent( 'pfunc_time_error' ) . '</strong>';
+		} else {
+			$this->mTimeChars += strlen( $format );
+			if ( $this->mTimeChars > $this->mMaxTimeChars ) {
+				wfLoadExtensionMessages( 'ParserFunctions' );
+				return '<strong class="error">' . wfMsgForContent( 'pfunc_time_too_long' ) . '</strong>';
+			} else {
+				
+				if ( method_exists( $wgContLang, 'sprintfDate' ) ) {
+					$result = $wgContLang->sprintfDate( $format, $ts );
+				} else {
+					if ( !class_exists( 'SprintfDateCompat' ) ) {
+						require( dirname( __FILE__ ) . '/SprintfDateCompat.php' );
+					}
+
+					$result = SprintfDateCompat::sprintfDate( $format, $ts );
+				}
+			}
+		}
+		$this->mTimeCache[$format][$date][$local] = $result;
+		return $result;
+	}
+
+	function localTime( &$parser, $format = '', $date = '' ) {
+		return $this->time( $parser, $format, $date, true );
+	}
+
+	/**
+	 * Obtain a specified number of slash-separated parts of a title,
+	 * e.g. {{#titleparts:Hello/World|1}} => "Hello"
+	 *
+	 * @param Parser $parser Parent parser
+	 * @param string $title Title to split
+	 * @param int $parts Number of parts to keep
+	 * @param int $offset Offset starting at 1
+	 * @return string
+	 */
+	public function titleparts( $parser, $title = '', $parts = 0, $offset = 0) {
+		$parts = intval( $parts );
+		$offset = intval( $offset );
+		$ntitle = Title::newFromText( $title );
+		if ( $ntitle instanceof Title ) {
+			$bits = explode( '/', $ntitle->getPrefixedText(), 25 );
+			if ( count( $bits ) <= 0 ) {
+				 return $ntitle->getPrefixedText();
+			} else {
+				if ( $offset > 0 ) {
+					--$offset;
+				}
+				if ( $parts == 0 ) {
+					return implode( '/', array_slice( $bits, $offset ) );
+				} else {
+					return implode( '/', array_slice( $bits, $offset, $parts ) );
+				}
+			}
+		} else {
+			return $title;
+		}
+	}
+
+	// Verifies parameter is less than max string length.
+	private function checkLength( $text ) {
+		global $wgPFStringLengthLimit;
+		return ( mb_strlen( $text ) < $wgPFStringLengthLimit );
+	}
+
+	// Generates error message.  Called when string is too long.
+	private function tooLongError() {
+		global $wgPFStringLengthLimit, $wgContLang;
+		wfLoadExtensionMessages( 'ParserFunctions' );
+		
+		return '<strong class="error">' . 
+			wfMsgExt( 'pfunc_string_too_long',
+				array( 'escape', 'parsemag', 'content' ), 
+				$wgContLang->formatNum( $wgPFStringLengthLimit ) ) .
+			'</strong>';
+	}
+
+	/**
+	 * {{#len:string}}
+	 * 
+	 * Reports number of characters in string.
+	 */
+	function runLen ( &$parser, $inStr = '' ) {
+		wfProfileIn( __METHOD__ );
+
+		$inStr = $this->killMarkers( $parser, (string)$inStr );
+		$len = mb_strlen( $inStr );
+
+		wfProfileOut( __METHOD__ );
+		return $len;
+	}
+
+	/**
+	 * {{#pos: string | needle | offset}}
+	 *
+	 * Finds first occurrence of "needle" in "string" starting at "offset".
+	 *
+	 * Note: If the needle is an empty string, single space is used instead.
+	 * Note: If the needle is not found, empty string is returned.
+	 */
+	function runPos ( &$parser, $inStr = '', $inNeedle = '', $inOffset = 0 ) {
+		wfProfileIn( __METHOD__ );
+
+		$inStr = $this->killMarkers( (string)$inStr );
+		$inNeedle = $this->killMarkers( (string)$inNeedle );
+		
+		if( !$this->checkLength( $inStr ) || 
+		    !$this->checkLength( $inNeedle ) ) {
+			wfProfileOut( __METHOD__ );
+			return $this->tooLongError();
+		}
+
+		if( $inNeedle == '' ) { $inNeedle = ' '; }
+
+		$pos = mb_strpos( $inStr, $inNeedle, $inOffset );
+		if( $pos === false ) { $pos = ""; }
+
+		wfProfileOut( __METHOD__ );
+		return $pos;
+	}
+
+	/**
+	 * {{#rpos: string | needle}}
+	 *
+	 * Finds last occurrence of "needle" in "string".
+	 *
+	 * Note: If the needle is an empty string, single space is used instead.
+	 * Note: If the needle is not found, -1 is returned.
+	 */
+	function runRPos ( &$parser, $inStr = '', $inNeedle = '' ) {
+		wfProfileIn( __METHOD__ );
+
+		$inStr = $this->killMarkers( (string)$inStr );
+		$inNeedle = $this->killMarkers( (string)$inNeedle );
+		
+		if( !$this->checkLength( $inStr ) || 
+		    !$this->checkLength( $inNeedle ) ) {
+			wfProfileOut( __METHOD__ );
+			return $this->tooLongError();
+		}
+
+		if( $inNeedle == '' ) { $inNeedle = ' '; }
+
+		$pos = mb_strrpos( $inStr, $inNeedle );
+		if( $pos === false ) { $pos = -1; }
+
+		wfProfileOut( __METHOD__ );
+		return $pos;
+	}
+
+	/**
+	 * {{#sub: string | start | length }}
+	 *
+	 * Returns substring of "string" starting at "start" and having
+	 * "length" characters.	
+	 *
+	 * Note: If length is zero, the rest of the input is returned.
+	 * Note: A negative value for "start" operates from the end of the 
+	 *   "string".
+	 * Note: A negative value for "length" returns a string reduced in
+	 *   length by that amount.
+	 */
+	function runSub ( &$parser, $inStr = '', $inStart = 0, $inLength = 0 ) {
+		wfProfileIn( __METHOD__ );
+
+		$inStr = $this->killMarkers( (string)$inStr );
+
+		if( !$this->checkLength( $inStr ) ) {
+			wfProfileOut( __METHOD__ );
+			return $this->tooLongError();
+		}
+		
+		if ( intval($inLength) == 0 ) {
+			$result = mb_substr( $inStr, $inStart );
+		} else {
+			$result = mb_substr( $inStr, $inStart, $inLength );
+		}
+
+		wfProfileOut( __METHOD__ );
+		return $result;	
+	}
+
+	/**
+	 * {{#count: string | substr }}
+	 *
+	 * Returns number of occurrences of "substr" in "string".
+	 *
+	 * Note: If "substr" is empty, a single space is used.
+	 */
+	function runCount ( &$parser, $inStr = '', $inSubStr = '' ) {
+		wfProfileIn( __METHOD__ );
+
+		$inStr = $this->killMarkers( (string)$inStr );
+		$inSubStr = $this->killMarkers( (string)$inSubStr );
+
+		if( !$this->checkLength( $inStr ) ||
+		    !$this->checkLength( $inSubStr ) ) {
+			wfProfileOut( __METHOD__ );
+			return $this->tooLongError();
+		}
+
+		if( $inSubStr == '' ) { $inSubStr = ' '; }
+			
+		$result = mb_substr_count( $inStr, $inSubStr );
+
+		wfProfileOut( __METHOD__ );
+		return $result;	
+	}
+
+	/**
+	 * {{#replace:string | from | to | limit }}
+	 *
+	 * Replaces each occurrence of "from" in "string" with "to".
+	 * At most "limit" replacements are performed.
+	 * 
+	 * Note: Armored against replacements that would generate huge strings.
+	 * Note: If "from" is an empty string, single space is used instead.
+	 */
+	function runReplace( &$parser, $inStr = '', 
+			$inReplaceFrom = '', $inReplaceTo = '', $inLimit = -1 ) {
+		global $wgPFStringLengthLimit;
+		wfProfileIn( __METHOD__ );
+
+		$inStr = $this->killMarkers( (string)$inStr );
+		$inReplaceFrom = $this->killMarkers( (string)$inReplaceFrom );
+		$inReplaceTo = $this->killMarkers( (string)$inReplaceTo );
+
+		if( !$this->checkLength( $inStr ) ||
+		    !$this->checkLength( $inReplaceFrom ) ||
+		    !$this->checkLength( $inReplaceTo ) ) { 
+			wfProfileOut( __METHOD__ );
+			return $this->tooLongError();
+		}
+
+		if( $inReplaceFrom == '' ) { $inReplaceFrom = ' '; }
+
+		// Precompute limit to avoid generating enormous string:
+		$diff = mb_strlen( $inReplaceTo ) - mb_strlen( $inReplaceFrom );
+		if( $diff > 0 ) {
+			$limit = ( ( $wgPFStringLengthLimit - mb_strlen( $inStr ) ) / $diff ) + 1;
+		} else {
+			$limit = -1;
+		}
+
+		$inLimit = intval($inLimit);
+		if( $inLimit >= 0 ) {
+			if( $limit > $inLimit || $limit == -1 ) { $limit = $inLimit; }
+		}
+
+		// Use regex to allow limit and handle UTF-8 correctly.
+		$inReplaceFrom = preg_quote( $inReplaceFrom, '/' );
+		$inReplaceTo = preg_quote( $inReplaceTo, '/' );
+
+		$result = preg_replace( '/' . $inReplaceFrom . '/u', 
+						$inReplaceTo, $inStr, $limit);
+
+		if( !$this->checkLength( $result ) ) {
+			wfProfileOut( __METHOD__ );
+			return $this->tooLongError();
+		}
+
+		wfProfileOut( __METHOD__ );
+		return $result;
+	}
+
+
+	/**
+	 * {{#explode:string | delimiter | position}}
+	 *
+	 * Breaks "string" into chunks separated by "delimiter" and returns the
+	 * chunk identified by "position".
+	 *
+	 * Note: Negative position can be used to specify tokens from the end.
+	 * Note: If the divider is an empty string, single space is used instead.
+	 * Note: Empty string is returned if there are not enough exploded chunks.
+	 */
+	function runExplode ( &$parser, $inStr = '', $inDiv = '', $inPos = 0 ) {
+		wfProfileIn( __METHOD__ );
+
+		$inStr = $this->killMarkers( (string)$inStr );
+		$inDiv = $this->killMarkers( (string)$inDiv );
+
+		if( $inDiv == '' ) { $inDiv = ' '; }
+
+		if( !$this->checkLength( $inStr ) ||
+		    !$this->checkLength( $inDiv ) ) { 
+			wfProfileOut( __METHOD__ );
+			return $this->tooLongError();
+		}
+
+		$inStr = preg_quote( $inStr, '/' );
+		$inDiv = preg_quote( $inDiv, '/' );
+		
+		$matches = preg_split( '/'.$inDiv.'/u', $inStr );
+		
+		if( $inPos >= 0 && isset( $matches[$inPos] ) ) {
+			$result = $matches[$inPos];
+		} elseif ( $inPos < 0 && isset( $matches[count($matches) + $inPos] ) ) {
+			$result = $matches[count($matches) + $inPos];
+		} else {
+			$result = '';
+		}
+
+		wfProfileOut( __METHOD__ );
+		return $result;
+	}
+}
+

Property changes on: trunk/extensions/ParserFunctions/ParserFunctions_body.php
___________________________________________________________________
Name: svn:eol-style
   + native

Index: trunk/extensions/ParserFunctions/ParserFunctions.php
===================================================================
--- trunk/extensions/ParserFunctions/ParserFunctions.php	(revision 51496)
+++ trunk/extensions/ParserFunctions/ParserFunctions.php	(revision 51497)
@@ -4,6 +4,34 @@
 	die( 'This file is a MediaWiki extension, it is not a valid entry point' );
 }
 
+
+/**
+ * CONFIGURATION 
+ * These variables may be overridden in LocalSettings.php after you include the
+ * extension file.
+ */
+
+/**
+ * Defines the maximum length of a string that string functions are allowed to operate on
+ * Prevention against denial of service by string function abuses.
+ */
+$wgPFStringLengthLimit = 1000;
+
+/**
+ * Enable string functions.
+ *
+ * Set this to true if you want your users to be able to implement their own 
+ * parsers in the ugliest, most inefficient programming language known to man: 
+ * MediaWiki wikitext with ParserFunctions.
+ *
+ * WARNING: enabling this may have an adverse impact on the sanity of your users.
+ * An alternative, saner solution for embedding complex text processing in 
+ * MediaWiki templates can be found at: http://www.mediawiki.org/wiki/Extension:Lua
+ */
+$wgPFEnableStringFunctions = false;
+
+
+/** REGISTRATION */
 $wgExtensionFunctions[] = 'wfSetupParserFunctions';
 $wgExtensionCredits['parserhook'][] = array(
 	'path' => __FILE__,
@@ -15,27 +43,48 @@
 	'descriptionmsg' => 'pfunc_desc',
 );
 
+$wgAutoloadClasses['ExtParserFunctions'] = dirname(__FILE__).'/ParserFunctions_body.php';
 $wgExtensionMessagesFiles['ParserFunctions'] = dirname(__FILE__) . '/ParserFunctions.i18n.php';
 $wgHooks['LanguageGetMagic'][]       = 'wfParserFunctionsLanguageGetMagic';
-$wgHooks['ParserAfterStrip'][] 	 = 'ExtParserFunctions::loadRegex';
 
 $wgParserTestFiles[] = dirname( __FILE__ ) . "/funcsParserTests.txt";
 
-//Defines the maximum length of a string that string functions are allowed to operate on
-//Prevention against denial of service by string function abuses.
-if( !isset($wgStringFunctionsLimit) ) {
-	$wgStringFunctionsLimit = 1000;
+
+function wfSetupParserFunctions() {
+	global $wgParser, $wgPFHookStub, $wgHooks;
+
+	$wgPFHookStub = new ParserFunctions_HookStub;
+
+	// Check for SFH_OBJECT_ARGS capability
+	if ( defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' ) ) {
+		$wgHooks['ParserFirstCallInit'][] = array( &$wgPFHookStub, 'registerParser' );
+	} else {
+		if ( class_exists( 'StubObject' ) && !StubObject::isRealObject( $wgParser ) ) {
+			$wgParser->_unstub();
+		}
+		$wgPFHookStub->registerParser( $wgParser );
+	}
+
+	$wgHooks['ParserClearState'][] = array( &$wgPFHookStub, 'clearState' );
 }
 
-class ExtParserFunctions {
-	var $mExprParser;
-	var $mTimeCache = array();
-	var $mTimeChars = 0;
-	var $mMaxTimeChars = 6000; # ~10 seconds
+function wfParserFunctionsLanguageGetMagic( &$magicWords, $langCode ) {
+	require_once( dirname( __FILE__ ) . '/ParserFunctions.i18n.magic.php' );
+	foreach( efParserFunctionsWords( $langCode ) as $word => $trans )
+		$magicWords[$word] = $trans;
+	return true;
+}
 
-	static $markerRegex = false;
+/**
+ * Stub class to defer loading of the bulk of the code until a parser function is
+ * actually used.
+ */
+class ParserFunctions_HookStub {
+	var $realObj;
 
 	function registerParser( &$parser ) {
+		global $wgPFEnableStringFunctions;
+
 		if ( defined( get_class( $parser ) . '::SFH_OBJECT_ARGS' ) ) {
 			// These functions accept DOM-style arguments
 			$parser->setFunctionHook( 'if', array( &$this, 'ifObj' ), SFH_OBJECT_ARGS );
@@ -60,798 +109,33 @@
 		$parser->setFunctionHook( 'titleparts', array( &$this, 'titleparts' ) );
 
 		//String Functions
-		$parser->setFunctionHook( 'len',      array(&$this, 'runLen'      ));
-		$parser->setFunctionHook( 'pos',      array(&$this, 'runPos'      ));
-		$parser->setFunctionHook( 'rpos',     array(&$this, 'runRPos'     ));
-		$parser->setFunctionHook( 'sub',      array(&$this, 'runSub'      ));
-		$parser->setFunctionHook( 'count',    array(&$this, 'runCount'    ));
-		$parser->setFunctionHook( 'replace',  array(&$this, 'runReplace'  ));
-		$parser->setFunctionHook( 'explode',  array(&$this, 'runExplode'  ));
+		if ( $wgPFEnableStringFunctions ) {
+			$parser->setFunctionHook( 'len',      array(&$this, 'runLen'      ));
+			$parser->setFunctionHook( 'pos',      array(&$this, 'runPos'      ));
+			$parser->setFunctionHook( 'rpos',     array(&$this, 'runRPos'     ));
+			$parser->setFunctionHook( 'sub',      array(&$this, 'runSub'      ));
+			$parser->setFunctionHook( 'count',    array(&$this, 'runCount'    ));
+			$parser->setFunctionHook( 'replace',  array(&$this, 'runReplace'  ));
+			$parser->setFunctionHook( 'explode',  array(&$this, 'runExplode'  ));
+		}
 
 		return true;
 	}
 
-	function clearState(&$parser) {
-		$this->mTimeChars = 0;
-		$parser->pf_ifexist_breakdown = array();
+	/** Defer ParserClearState */
+	function clearState( &$parser ) {
+		if ( !is_null( $this->realObj ) ) {
+			$this->realObj->clearState( $parser );
+		}
 		return true;
 	}
 
-	/* Called by ParserAfterStrip.  Preloads the syntax for unique markers
-	   so that we can avoid reconstructing it on every operation.  */
-	static function loadRegex( &$parser ) {
-		wfProfileIn( __METHOD__ );
-
-		$prefix = preg_quote( $parser->uniqPrefix(), '/' );
-
-		// The first line represents Parser from release 1.12 forward.
-		// subsequent lines are hacks to accomodate old Mediawiki versions.
-		if ( defined('Parser::MARKER_SUFFIX') )
-			$suffix = preg_quote( Parser::MARKER_SUFFIX, '/' );
-		elseif ( isset($parser->mMarkerSuffix) )
-			$suffix = preg_quote( $parser->mMarkerSuffix, '/' );
-		elseif ( defined('MW_PARSER_VERSION') && 
-				strcmp( MW_PARSER_VERSION, '1.6.1' ) > 0 )
-			$suffix = "QINU\x07";
-		else $suffix = 'QINU';
-		
-		self::$markerRegex = '/' .$prefix. '(?:(?!' .$suffix. ').)*' . $suffix . '/us';
-
-		wfProfileOut( __METHOD__ );
-		return true;
-	}
-
-	// Removes unique markers from passed parameters, used by string functions.
-	private function killMarkers ( $text ) {
-		if( self::$markerRegex ) {
-			return preg_replace( self::$markerRegex , '' , $text );
-		} else {
-			return $text;
+	/** Pass through function call */
+	function __call( $name, $args ) {
+		if ( is_null( $this->realObj ) ) {
+			$this->realObj = new ExtParserFunctions;
+			$this->realObj->clearState( $args[0] );
 		}
+		return call_user_func_array( array( $this->realObj, $name ), $args );
 	}
-
-	function &getExprParser() {
-		if ( !isset( $this->mExprParser ) ) {
-			if ( !class_exists( 'ExprParser' ) ) {
-				require( dirname( __FILE__ ) . '/Expr.php' );
-			}
-			$this->mExprParser = new ExprParser;
-		}
-		return $this->mExprParser;
-	}
-
-	function expr( &$parser, $expr = '' ) {
-		try {
-			return $this->getExprParser()->doExpression( $expr );
-		} catch(ExprError $e) {
-			return $e->getMessage();
-		}
-	}
-
-	function ifexpr( &$parser, $expr = '', $then = '', $else = '' ) {
-		try{
-			if($this->getExprParser()->doExpression( $expr )) {
-				return $then;
-			} else {
-				return $else;
-			}
-		} catch (ExprError $e){
-			return $e->getMessage();
-		}
-	}
-
-	function ifexprObj( $parser, $frame, $args ) {
-		$expr = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
-		$then = isset( $args[1] ) ? $args[1] : '';
-		$else = isset( $args[2] ) ? $args[2] : '';
-		$result = $this->ifexpr( $parser, $expr, $then, $else );
-		if ( is_object( $result ) ) {
-			$result = trim( $frame->expand( $result ) );
-		}
-		return $result;
-	}
-
-	function ifHook( &$parser, $test = '', $then = '', $else = '' ) {
-		if ( $test !== '' ) {
-			return $then;
-		} else {
-			return $else;
-		}
-	}
-
-	function ifObj( &$parser, $frame, $args ) {
-		$test = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
-		if ( $test !== '' ) {
-			return isset( $args[1] ) ? trim( $frame->expand( $args[1] ) ) : '';
-		} else {
-			return isset( $args[2] ) ? trim( $frame->expand( $args[2] ) ) : '';
-		}
-	}
-
-	function ifeq( &$parser, $left = '', $right = '', $then = '', $else = '' ) {
-		if ( $left == $right ) {
-			return $then;
-		} else {
-			return $else;
-		}
-	}
-
-	function ifeqObj( &$parser, $frame, $args ) {
-		$left = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
-		$right = isset( $args[1] ) ? trim( $frame->expand( $args[1] ) ) : '';
-		if ( $left == $right ) {
-			return isset( $args[2] ) ? trim( $frame->expand( $args[2] ) ) : '';
-		} else {
-			return isset( $args[3] ) ? trim( $frame->expand( $args[3] ) ) : '';
-		}
-	}
-
-	function iferror( &$parser, $test = '', $then = '', $else = false ) {
-		if ( preg_match( '/<(?:strong|span|p|div)\s(?:[^\s>]*\s+)*?class="(?:[^"\s>]*\s+)*?error(?:\s[^">]*)?"/', $test ) ) {
-			return $then;
-		} elseif ( $else === false ) {
-			return $test;
-		} else {
-			return $else;
-		}
-	}
-
-	function iferrorObj( &$parser, $frame, $args ) {
-		$test = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
-		$then = isset( $args[1] ) ? $args[1] : false;
-		$else = isset( $args[2] ) ? $args[2] : false;
-		$result = $this->iferror( $parser, $test, $then, $else );
-		if ( $result === false ) {
-			return '';
-		} else {
-			return trim( $frame->expand( $result ) );
-		}
-	}
-
-	function switchHook( &$parser /*,...*/ ) {
-		$args = func_get_args();
-		array_shift( $args );
-		$primary = trim(array_shift($args));
-		$found = false;
-		$parts = null;
-		$default = null;
-		$mwDefault =& MagicWord::get( 'default' );
-		foreach( $args as $arg ) {
-			$parts = array_map( 'trim', explode( '=', $arg, 2 ) );
-			if ( count( $parts ) == 2 ) {
-				# Found "="
-				if ( $found || $parts[0] == $primary ) {
-					# Found a match, return now
-					return $parts[1];
-				} else {
-					if ( $mwDefault->matchStartAndRemove( $parts[0] ) ) {
-						$default = $parts[1];
-					} # else wrong case, continue
-				}
-			} elseif ( count( $parts ) == 1 ) {
-				# Multiple input, single output
-				# If the value matches, set a flag and continue
-				if ( $parts[0] == $primary ) {
-					$found = true;
-				}
-			} # else RAM corruption due to cosmic ray?
-		}
-		# Default case
-		# Check if the last item had no = sign, thus specifying the default case
-		if ( count( $parts ) == 1) {
-			return $parts[0];
-		} elseif ( !is_null( $default ) ) {
-			return $default;
-		} else {
-			return '';
-		}
-	}
-
-	function switchObj( $parser, $frame, $args ) {
-		if ( count( $args ) == 0 ) {
-			return '';
-		}
-		$primary = trim( $frame->expand( array_shift( $args ) ) );
-		$found = false;
-		$default = null;
-		$lastItemHadNoEquals = false;
-		$mwDefault =& MagicWord::get( 'default' );
-		foreach ( $args as $arg ) {
-			$bits = $arg->splitArg();
-			$nameNode = $bits['name'];
-			$index = $bits['index'];
-			$valueNode = $bits['value'];
-
-			if ( $index === '' ) {
-				# Found "="
-				$lastItemHadNoEquals = false;
-				$test = trim( $frame->expand( $nameNode ) );
-				if ( $found ) {
-					# Multiple input match
-					return trim( $frame->expand( $valueNode ) );
-				} else {
-					$test = trim( $frame->expand( $nameNode ) );
-					if ( $test == $primary ) {
-						# Found a match, return now
-						return trim( $frame->expand( $valueNode ) );
-					} else {
-						if ( $mwDefault->matchStartAndRemove( $test ) ) {
-							$default = $valueNode;
-						} # else wrong case, continue
-					}
-				}
-			} else {
-				# Multiple input, single output
-				# If the value matches, set a flag and continue
-				$lastItemHadNoEquals = true;
-				$test = trim( $frame->expand( $valueNode ) );
-				if ( $test == $primary ) {
-					$found = true;
-				}
-			}
-		}
-		# Default case
-		# Check if the last item had no = sign, thus specifying the default case
-		if ( $lastItemHadNoEquals ) {
-			return $test;
-		} elseif ( !is_null( $default ) ) {
-			return trim( $frame->expand( $default ) );
-		} else {
-			return '';
-		}
-	}
-
-	/**
-	 * Returns the absolute path to a subpage, relative to the current article
-	 * title. Treats titles as slash-separated paths.
-	 *
-	 * Following subpage link syntax instead of standard path syntax, an
-	 * initial slash is treated as a relative path, and vice versa.
-	 */
-	public function rel2abs( &$parser , $to = '' , $from = '' ) {
-
-		$from = trim($from);
-		if( $from == '' ) {
-			$from = $parser->getTitle()->getPrefixedText();
-		}
-
-		$to = rtrim( $to , ' /' );
-
-		// if we have an empty path, or just one containing a dot
-		if( $to == '' || $to == '.' ) {
-			return $from;
-		}
-
-		// if the path isn't relative
-		if ( substr( $to , 0 , 1) != '/' &&
-		 substr( $to , 0 , 2) != './' &&
-		 substr( $to , 0 , 3) != '../' &&
-		 $to != '..' )
-		{
-			$from = '';
-		}
-		// Make a long path, containing both, enclose it in /.../
-		$fullPath = '/' . $from . '/' .  $to . '/';
-
-		// remove redundant current path dots
-		$fullPath = preg_replace( '!/(\./)+!', '/', $fullPath );
-
-		// remove double slashes
-		$fullPath = preg_replace( '!/{2,}!', '/', $fullPath );
-
-		// remove the enclosing slashes now
-		$fullPath = trim( $fullPath , '/' );
-		$exploded = explode ( '/' , $fullPath );
-		$newExploded = array();
-
-		foreach ( $exploded as $current ) {
-			if( $current == '..' ) { // removing one level
-				if( !count( $newExploded ) ){
-					// attempted to access a node above root node
-					wfLoadExtensionMessages( 'ParserFunctions' );
-					return '<strong class="error">' . wfMsgForContent( 'pfunc_rel2abs_invalid_depth', $fullPath ) . '</strong>';
-				}
-				// remove last level from the stack
-				array_pop( $newExploded );
-			} else {
-				// add the current level to the stack
-				$newExploded[] = $current;
-			}
-		}
-
-		// we can now join it again
-		return implode( '/' , $newExploded );
-	}
-
-	function incrementIfexistCount( $parser, $frame ) {
-		// Don't let this be called more than a certain number of times. It tends to make the database explode.
-		global $wgExpensiveParserFunctionLimit;
-		$parser->mExpensiveFunctionCount++;
-		if ( $frame ) {
-			$pdbk = $frame->getPDBK( 1 );
-			if ( !isset( $parser->pf_ifexist_breakdown[$pdbk] ) ) {
-				$parser->pf_ifexist_breakdown[$pdbk] = 0;
-			}
-			$parser->pf_ifexist_breakdown[$pdbk] ++;
-		}
-		return $parser->mExpensiveFunctionCount <= $wgExpensiveParserFunctionLimit;
-	}
-
-	function ifexist( &$parser, $title = '', $then = '', $else = '' ) {
-		return $this->ifexistCommon( $parser, false, $title, $then, $else );
-	}
-
-	function ifexistCommon( &$parser, $frame, $titletext = '', $then = '', $else = '' ) {
-		global $wgContLang;
-		$title = Title::newFromText( $titletext );
-		$wgContLang->findVariantLink( $titletext, $title, true );
-		if ( $title ) {
-			if( $title->getNamespace() == NS_MEDIA ) {
-				/* If namespace is specified as NS_MEDIA, then we want to
-				 * check the physical file, not the "description" page.
-				 */
-				if ( !$this->incrementIfexistCount( $parser, $frame ) ) {
-					return $else;
-				}
-				$file = wfFindFile($title);
-				if ( !$file ) {
-					return $else;
-				}
-				$parser->mOutput->addImage($file->getName());
-				return $file->exists() ? $then : $else;
-			} elseif( $title->getNamespace() == NS_SPECIAL ) {
-				/* Don't bother with the count for special pages,
-				 * since their existence can be checked without
-				 * accessing the database.
-				 */
-				return SpecialPage::exists( $title->getDBkey() ) ? $then : $else;
-			} elseif( $title->isExternal() ) {
-				/* Can't check the existence of pages on other sites,
-				 * so just return $else.  Makes a sort of sense, since
-				 * they don't exist _locally_.
-				 */
-				return $else;
-			} else {
-				$pdbk = $title->getPrefixedDBkey();
-				$lc = LinkCache::singleton();
-				if ( !$this->incrementIfexistCount( $parser, $frame ) ) {
-					return $else;
-				}
-				if ( 0 != ( $id = $lc->getGoodLinkID( $pdbk ) ) ) {
-					$parser->mOutput->addLink( $title, $id );
-					return $then;
-				} elseif ( $lc->isBadLink( $pdbk ) ) {
-					$parser->mOutput->addLink( $title, 0 );
-					return $else;
-				}
-				$id = $title->getArticleID();
-				$parser->mOutput->addLink( $title, $id );
-				if ( $id ) {
-					return $then;
-				}
-			}
-		}
-		return $else;
-	}
-
-	function ifexistObj( &$parser, $frame, $args ) {
-		$title = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : '';
-		$then = isset( $args[1] ) ? $args[1] : null;
-		$else = isset( $args[2] ) ? $args[2] : null;
-
-		$result = $this->ifexistCommon( $parser, $frame, $title, $then, $else );
-		if ( $result === null ) {
-			return '';
-		} else {
-			return trim( $frame->expand( $result ) );
-		}
-	}
-
-	function time( &$parser, $format = '', $date = '', $local = false ) {
-		global $wgContLang, $wgLocaltimezone;
-		if ( isset( $this->mTimeCache[$format][$date][$local] ) ) {
-			return $this->mTimeCache[$format][$date][$local];
-		}
-		
-		#compute the timestamp string $ts
-		#PHP >= 5.2 can handle dates before 1970 or after 2038 using the DateTime object
-		#PHP < 5.2 is limited to dates between 1970 and 2038
-		
-		$invalidTime = false;
-		
-		if ( class_exists( 'DateTime' ) ) { #PHP >= 5.2
-			# the DateTime constructor must be used because it throws exceptions 
-			# when errors occur, whereas date_create appears to just output a warning 
-			# that can't really be detected from within the code
-			try {
-				# Determine timezone
-				if ( $local ) {
-					 # convert to MediaWiki local timezone if set
-					if ( isset( $wgLocaltimezone ) ) {
-						$tz = new DateTimeZone( $wgLocaltimezone );
-					} else {
-						$tz = new DateTimeZone( 'UTC' );
-					}
-				} else {
-					# if local time was not requested, convert to UTC
-					$tz = new DateTimeZone( 'UTC' );
-				}
-
-				# Parse date
-				if ( $date !== '' ) {
-					$dateObject = new DateTime( $date, $tz );
-				} else {
-					# use current date and time
-					$dateObject = new DateTime( 'now', $tz );
-				}
-
-				# Generate timestamp
-				$ts = $dateObject->format( 'YmdHis' );
-			} catch (Exception $ex) {
-				$invalidTime = true;
-			}
-		} else { #PHP < 5.2
-			if ( $date !== '' ) {
-				$unix = @strtotime( $date );
-			} else {
-				$unix = time();
-			}
-			
-			if ( $unix == -1 || $unix == false ) {
-				$invalidTime = true;
-			} else {
-				if ( $local ) {
-					# Use the time zone
-					if ( isset( $wgLocaltimezone ) ) {
-						$oldtz = getenv( 'TZ' );
-						putenv( 'TZ='.$wgLocaltimezone );
-					}
-					wfSuppressWarnings(); // E_STRICT system time bitching
-					$ts = date( 'YmdHis', $unix );
-					wfRestoreWarnings();
-					if ( isset( $wgLocaltimezone ) ) {
-						putenv( 'TZ='.$oldtz );
-					}
-				} else {
-					$ts = wfTimestamp( TS_MW, $unix );
-				}
-			}
-		}
-		
-		#format the timestamp and return the result
-		if ( $invalidTime ) {
-			wfLoadExtensionMessages( 'ParserFunctions' );
-			$result = '<strong class="error">' . wfMsgForContent( 'pfunc_time_error' ) . '</strong>';
-		} else {
-			$this->mTimeChars += strlen( $format );
-			if ( $this->mTimeChars > $this->mMaxTimeChars ) {
-				wfLoadExtensionMessages( 'ParserFunctions' );
-				return '<strong class="error">' . wfMsgForContent( 'pfunc_time_too_long' ) . '</strong>';
-			} else {
-				
-				if ( method_exists( $wgContLang, 'sprintfDate' ) ) {
-					$result = $wgContLang->sprintfDate( $format, $ts );
-				} else {
-					if ( !class_exists( 'SprintfDateCompat' ) ) {
-						require( dirname( __FILE__ ) . '/SprintfDateCompat.php' );
-					}
-
-					$result = SprintfDateCompat::sprintfDate( $format, $ts );
-				}
-			}
-		}
-		$this->mTimeCache[$format][$date][$local] = $result;
-		return $result;
-	}
-
-	function localTime( &$parser, $format = '', $date = '' ) {
-		return $this->time( $parser, $format, $date, true );
-	}
-
-	/**
-	 * Obtain a specified number of slash-separated parts of a title,
-	 * e.g. {{#titleparts:Hello/World|1}} => "Hello"
-	 *
-	 * @param Parser $parser Parent parser
-	 * @param string $title Title to split
-	 * @param int $parts Number of parts to keep
-	 * @param int $offset Offset starting at 1
-	 * @return string
-	 */
-	public function titleparts( $parser, $title = '', $parts = 0, $offset = 0) {
-		$parts = intval( $parts );
-		$offset = intval( $offset );
-		$ntitle = Title::newFromText( $title );
-		if ( $ntitle instanceof Title ) {
-			$bits = explode( '/', $ntitle->getPrefixedText(), 25 );
-			if ( count( $bits ) <= 0 ) {
-				 return $ntitle->getPrefixedText();
-			} else {
-				if ( $offset > 0 ) {
-					--$offset;
-				}
-				if ( $parts == 0 ) {
-					return implode( '/', array_slice( $bits, $offset ) );
-				} else {
-					return implode( '/', array_slice( $bits, $offset, $parts ) );
-				}
-			}
-		} else {
-			return $title;
-		}
-	}
-
-	// Verifies parameter is less than max string length.
-	private function checkLength( $text ) {
-		global $wgStringFunctionsLimit;
-		return ( mb_strlen( $text ) < $wgStringFunctionsLimit );
-	}
-
-	// Generates error message.  Called when string is too long.
-	private function tooLongError() {
-		global $wgStringFunctionsLimit, $wgContLang;
-		wfLoadExtensionMessages( 'ParserFunctions' );
-		
-		return '<strong class="error">' . 
-			wfMsgExt( 'pfunc_string_too_long',
-				array( 'escape', 'parsemag', 'content' ), 
-				$wgContLang->formatNum( $wgStringFunctionsLimit ) ) .
-			'</strong>';
-	}
-
-	/**
-	 * {{#len:string}}
-	 * 
-	 * Reports number of characters in string.
-	 */
-	function runLen ( &$parser, $inStr = '' ) {
-		wfProfileIn( __METHOD__ );
-
-		$inStr = $this->killMarkers( (string)$inStr );
-		$len = mb_strlen( $inStr );
-
-		wfProfileOut( __METHOD__ );
-		return $len;
-	}
-
-	/**
-	 * {{#pos: string | needle | offset}}
-	 *
-	 * Finds first occurrence of "needle" in "string" starting at "offset".
-	 *
-	 * Note: If the needle is an empty string, single space is used instead.
-	 * Note: If the needle is not found, empty string is returned.
-	 */
-	function runPos ( &$parser, $inStr = '', $inNeedle = '', $inOffset = 0 ) {
-		wfProfileIn( __METHOD__ );
-
-		$inStr = $this->killMarkers( (string)$inStr );
-		$inNeedle = $this->killMarkers( (string)$inNeedle );
-		
-		if( !$this->checkLength( $inStr ) || 
-		    !$this->checkLength( $inNeedle ) ) {
-			wfProfileOut( __METHOD__ );
-			return $this->tooLongError();
-		}
-
-		if( $inNeedle == '' ) { $inNeedle = ' '; }
-
-		$pos = mb_strpos( $inStr, $inNeedle, $inOffset );
-		if( $pos === false ) { $pos = ""; }
-
-		wfProfileOut( __METHOD__ );
-		return $pos;
-	}
-
-	/**
-	 * {{#rpos: string | needle}}
-	 *
-	 * Finds last occurrence of "needle" in "string".
-	 *
-	 * Note: If the needle is an empty string, single space is used instead.
-	 * Note: If the needle is not found, -1 is returned.
-	 */
-	function runRPos ( &$parser, $inStr = '', $inNeedle = '' ) {
-		wfProfileIn( __METHOD__ );
-
-		$inStr = $this->killMarkers( (string)$inStr );
-		$inNeedle = $this->killMarkers( (string)$inNeedle );
-		
-		if( !$this->checkLength( $inStr ) || 
-		    !$this->checkLength( $inNeedle ) ) {
-			wfProfileOut( __METHOD__ );
-			return $this->tooLongError();
-		}
-
-		if( $inNeedle == '' ) { $inNeedle = ' '; }
-
-		$pos = mb_strrpos( $inStr, $inNeedle );
-		if( $pos === false ) { $pos = -1; }
-
-		wfProfileOut( __METHOD__ );
-		return $pos;
-	}
-
-	/**
-	 * {{#sub: string | start | length }}
-	 *
-	 * Returns substring of "string" starting at "start" and having
-	 * "length" characters.	
-	 *
-	 * Note: If length is zero, the rest of the input is returned.
-	 * Note: A negative value for "start" operates from the end of the 
-	 *   "string".
-	 * Note: A negative value for "length" returns a string reduced in
-	 *   length by that amount.
-	 */
-	function runSub ( &$parser, $inStr = '', $inStart = 0, $inLength = 0 ) {
-		wfProfileIn( __METHOD__ );
-
-		$inStr = $this->killMarkers( (string)$inStr );
-
-		if( !$this->checkLength( $inStr ) ) {
-			wfProfileOut( __METHOD__ );
-			return $this->tooLongError();
-		}
-		
-		if ( intval($inLength) == 0 ) {
-			$result = mb_substr( $inStr, $inStart );
-		} else {
-			$result = mb_substr( $inStr, $inStart, $inLength );
-		}
-
-		wfProfileOut( __METHOD__ );
-		return $result;	
-	}
-
-	/**
-	 * {{#count: string | substr }}
-	 *
-	 * Returns number of occurrences of "substr" in "string".
-	 *
-	 * Note: If "substr" is empty, a single space is used.
-	 */
-	function runCount ( &$parser, $inStr = '', $inSubStr = '' ) {
-		wfProfileIn( __METHOD__ );
-
-		$inStr = $this->killMarkers( (string)$inStr );
-		$inSubStr = $this->killMarkers( (string)$inSubStr );
-
-		if( !$this->checkLength( $inStr ) ||
-		    !$this->checkLength( $inSubStr ) ) {
-			wfProfileOut( __METHOD__ );
-			return $this->tooLongError();
-		}
-
-		if( $inSubStr == '' ) { $inSubStr = ' '; }
-			
-		$result = mb_substr_count( $inStr, $inSubStr );
-
-		wfProfileOut( __METHOD__ );
-		return $result;	
-	}
-
-	/**
-	 * {{#replace:string | from | to | limit }}
-	 *
-	 * Replaces each occurrence of "from" in "string" with "to".
-	 * At most "limit" replacements are performed.
-	 * 
-	 * Note: Armored against replacements that would generate huge strings.
-	 * Note: If "from" is an empty string, single space is used instead.
-	 */
-	function runReplace( &$parser, $inStr = '', 
-			$inReplaceFrom = '', $inReplaceTo = '', $inLimit = -1 ) {
-		global $wgStringFunctionsLimit;
-		wfProfileIn( __METHOD__ );
-
-		$inStr = $this->killMarkers( (string)$inStr );
-		$inReplaceFrom = $this->killMarkers( (string)$inReplaceFrom );
-		$inReplaceTo = $this->killMarkers( (string)$inReplaceTo );
-
-		if( !$this->checkLength( $inStr ) ||
-		    !$this->checkLength( $inReplaceFrom ) ||
-		    !$this->checkLength( $inReplaceTo ) ) { 
-			wfProfileOut( __METHOD__ );
-			return $this->tooLongError();
-		}
-
-		if( $inReplaceFrom == '' ) { $inReplaceFrom = ' '; }
-
-		// Precompute limit to avoid generating enormous string:
-		$diff = mb_strlen( $inReplaceTo ) - mb_strlen( $inReplaceFrom );
-		if( $diff > 0 ) {
-			$limit = ( ( $wgStringFunctionsLimit - mb_strlen( $inStr ) ) / $diff ) + 1;
-		} else {
-			$limit = -1;
-		}
-
-		$inLimit = intval($inLimit);
-		if( $inLimit >= 0 ) {
-			if( $limit > $inLimit || $limit == -1 ) { $limit = $inLimit; }
-		}
-
-		// Use regex to allow limit and handle UTF-8 correctly.
-		$inReplaceFrom = preg_quote( $inReplaceFrom, '/' );
-		$inReplaceTo = preg_quote( $inReplaceTo, '/' );
-
-		$result = preg_replace( '/' . $inReplaceFrom . '/u', 
-						$inReplaceTo, $inStr, $limit);
-
-		if( !$this->checkLength( $result ) ) {
-			wfProfileOut( __METHOD__ );
-			return $this->tooLongError();
-		}
-
-		wfProfileOut( __METHOD__ );
-		return $result;
-	}
-
-
-	/**
-	 * {{#explode:string | delimiter | position}}
-	 *
-	 * Breaks "string" into chunks separated by "delimiter" and returns the
-	 * chunk identified by "position".
-	 *
-	 * Note: Negative position can be used to specify tokens from the end.
-	 * Note: If the divider is an empty string, single space is used instead.
-	 * Note: Empty string is returned if there are not enough exploded chunks.
-	 */
-	function runExplode ( &$parser, $inStr = '', $inDiv = '', $inPos = 0 ) {
-		wfProfileIn( __METHOD__ );
-
-		$inStr = $this->killMarkers( (string)$inStr );
-		$inDiv = $this->killMarkers( (string)$inDiv );
-
-		if( $inDiv == '' ) { $inDiv = ' '; }
-
-		if( !$this->checkLength( $inStr ) ||
-		    !$this->checkLength( $inDiv ) ) { 
-			wfProfileOut( __METHOD__ );
-			return $this->tooLongError();
-		}
-
-		$inStr = preg_quote( $inStr, '/' );
-		$inDiv = preg_quote( $inDiv, '/' );
-		
-		$matches = preg_split( '/'.$inDiv.'/u', $inStr );
-		
-		if( $inPos >= 0 && isset( $matches[$inPos] ) ) {
-			$result = $matches[$inPos];
-		} elseif ( $inPos < 0 && isset( $matches[count($matches) + $inPos] ) ) {
-			$result = $matches[count($matches) + $inPos];
-		} else {
-			$result = '';
-		}
-
-		wfProfileOut( __METHOD__ );
-		return $result;
-	}
 }
-
-function wfSetupParserFunctions() {
-	global $wgParser, $wgExtParserFunctions, $wgHooks;
-
-	$wgExtParserFunctions = new ExtParserFunctions;
-
-	// Check for SFH_OBJECT_ARGS capability
-	if ( defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' ) ) {
-		$wgHooks['ParserFirstCallInit'][] = array( &$wgExtParserFunctions, 'registerParser' );
-	} else {
-		if ( class_exists( 'StubObject' ) && !StubObject::isRealObject( $wgParser ) ) {
-			$wgParser->_unstub();
-		}
-		$wgExtParserFunctions->registerParser( $wgParser );
-	}
-
-	$wgHooks['ParserClearState'][] = array( &$wgExtParserFunctions, 'clearState' );
-}
-
-function wfParserFunctionsLanguageGetMagic( &$magicWords, $langCode ) {
-	require_once( dirname( __FILE__ ) . '/ParserFunctions.i18n.magic.php' );
-	foreach( efParserFunctionsWords( $langCode ) as $word => $trans )
-		$magicWords[$word] = $trans;
-	return true;
-}
-

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

Views
Toolbox