Extension:ParserFunctions/Extended

From MediaWiki.org
Jump to: navigation, search

Script error Note: this extension has been useful when ParserFunctions was more limited than it is now. Most of the functionality of this extension is now in the regular ParserFunctions.

This extension consists of ParserFunctions after applying patch [1] by Carl Fürstenberg. The next section gives the resulting Expr.php after applying the patch to [2]. The following section gives the resulting Expr.php after applying the patch to [3], and adapting it to the changed error handling [4].

With this extension scientific notation is not only produced as output, but also allowed as input. Also the power sign ^ works.

Further it includes the PHP functions abs, floor, ceil, fmod, and sqrt, and a function idiv which applies conversion to an integer by the PHP function "(int)" (rounding towards zero) to the quotient of the arguments.

Finally it contains the functions #min and #max, which are the PHP functions min and max, for the case that each argument is numeric.

Examples:

  • {{#expr:2e3^10}} gives 1.024E+033
  • {{#expr:-10 fmod 7}} gives -3
  • {{#expr:-10 idiv 7}} gives -1
  • {{#expr:floor (-10/7)}} gives -2
  • {{#expr:ceil (-10/7)}} gives -1
  • {{#expr:abs-3}} gives 3
  • {{#expr:sqrt3}} gives 1.7320508075689
  • {{#min:4e3|3e4|35*450}} gives 4000
  • {{#max:4e3|3e4|35*450}} gives 30000

Expr.php (extended)[edit | edit source]

<?php
 
if ( !defined( 'MEDIAWIKI' ) ) {
	die( 'This file is a MediaWiki extension, it is not a valid entry point' );
}
 
// Character classes
define( 'EXPR_WHITE_CLASS', " \t\r\n" );
define( 'EXPR_NUMBER_CLASS', '0123456789.' );
 
// Token types
define( 'EXPR_WHITE', 1 );
define( 'EXPR_NUMBER', 2 );
define( 'EXPR_NEGATIVE', 3 );
define( 'EXPR_POSITIVE', 4 );
define( 'EXPR_PLUS', 5 );
define( 'EXPR_MINUS', 6 );
define( 'EXPR_TIMES', 7 );
define( 'EXPR_DIVIDE', 8 );
define( 'EXPR_MOD', 9 );
define( 'EXPR_OPEN', 10 );
define( 'EXPR_CLOSE', 11 );
define( 'EXPR_AND', 12 );
define( 'EXPR_OR', 13 );
define( 'EXPR_NOT', 14 );
define( 'EXPR_EQUALITY', 15 );
define( 'EXPR_LESS', 16 );
define( 'EXPR_GREATER', 17 );
define( 'EXPR_LESSEQ', 18 );
define( 'EXPR_GREATEREQ', 19 );
define( 'EXPR_NOTEQ', 20 );
define( 'EXPR_ROUND', 21 );
define( 'EXPR_INT_DIVIDE', 22); 
define( 'EXPR_CEIL', 23); 
define( 'EXPR_FLOOR', 24); 
define( 'EXPR_ABS', 25); 
define( 'EXPR_FLOAT_MOD', 26); 
define( 'EXPR_POW', 27); 
define( 'EXPR_SQRT', 28); 
define( 'EXPR_E', 29); 
 
class ExprError extends Exception {
	public function __construct($msg, $parameter = ''){
		$this->message = wfMsgForContent( "expr_$msg", htmlspecialchars( $parameter ) );
	}
}
 
class ExprParser {
	var $maxStackSize = 100;
 
	var $precedence = array( 
		EXPR_NEGATIVE => 10,
		EXPR_POSITIVE => 10,
                EXPR_E => 10,
		EXPR_NOT => 9,
		EXPR_ABS => 9, 
 		EXPR_CEIL => 9, 
 		EXPR_FLOOR => 9, 
 		EXPR_POW => 9, 
 		EXPR_SQRT => 9, 
		EXPR_TIMES => 8,
		EXPR_DIVIDE => 8,
		EXPR_INT_DIVIDE => 8,
		EXPR_MOD => 8,
		EXPR_FLOAT_MOD => 8,
		EXPR_PLUS => 6,
		EXPR_MINUS => 6,
		EXPR_ROUND => 5,
		EXPR_EQUALITY => 4,
		EXPR_LESS => 4,
		EXPR_GREATER => 4,
		EXPR_LESSEQ => 4,
		EXPR_GREATEREQ => 4,
		EXPR_NOTEQ => 4,
		EXPR_AND => 3,
		EXPR_OR => 2,
		EXPR_OPEN => -1,
		EXPR_CLOSE => -1
	);
 
	var $names = array(
		EXPR_NEGATIVE => '-',
		EXPR_POSITIVE => '+',
		EXPR_NOT => 'not',
		EXPR_TIMES => '*',
		EXPR_DIVIDE => '/',
		EXPR_MOD => 'mod',
		EXPR_FLOAT_MOD => 'fmod',
		EXPR_PLUS => '+',
		EXPR_MINUS => '-',
		EXPR_ROUND => 'round',
		EXPR_EQUALITY => '=',
		EXPR_LESS => '<',
		EXPR_GREATER => '>',
		EXPR_LESSEQ => '<=',
		EXPR_GREATEREQ => '>=',
		EXPR_NOTEQ => '<>',
		EXPR_AND => 'and',
		EXPR_OR => 'or',
		EXPR_INT_DIVIDE => 'idiv', 
 		EXPR_ABS => 'abs', 
 		EXPR_CEIL => 'ceil', 
 		EXPR_FLOOR => 'floor', 
 		EXPR_POW => '^', 
 		EXPR_SQRT => 'sqrt', 
 		EXPR_E => 'e', 
 
	);
 
 
	var $words = array(
		'mod' => EXPR_MOD,
		'and' => EXPR_AND,
		'or' => EXPR_OR,
		'not' => EXPR_NOT,
		'round' => EXPR_ROUND,
		'div' => EXPR_DIVIDE,
		'idiv' => EXPR_INT_DIVIDE, 
 		'floor' => EXPR_FLOOR, 
 		'ceil' => EXPR_CEIL, 
 		'abs' => EXPR_ABS, 
 		'fmod' => EXPR_FLOAT_MOD, 
 		'sqrt' => EXPR_SQRT, 
 		'e' => EXPR_E,  
	);
 
 
	/**
	 * Add expression messages to the message cache
	 * @static
	 */
	function addMessages() {
		global $wgMessageCache;
		$wgMessageCache->addMessages( array(
			'expr_stack_exhausted' => 'Expression error: Stack exhausted',
			'expr_unexpected_number' => 'Expression error: Unexpected number',
			'expr_preg_match_failure' => 'Expression error: Unexpected preg_match failure',
			'expr_unrecognised_word' => 'Expression error: Unrecognised word "$1"',
			'expr_unexpected_operator' => 'Expression error: Unexpected $1 operator',
			'expr_missing_operand' => 'Expression error: Missing operand for $1',
			'expr_unexpected_closing_bracket' => 'Expression error: Unexpected closing bracket',
			'expr_unrecognised_punctuation' => 'Expression error: Unrecognised punctuation character "$1"',
			'expr_unclosed_bracket' => 'Expression error: Unclosed bracket',
			'expr_division_by_zero' => 'Division by zero',
			'expr_unknown_error' => 'Expression error: Unknown error ($1)',
			'expr_not_a_number' => 'In $1: result is not a number',
		));
	}
	/**
	 * Evaluate a mathematical expression
	 *
	 * The algorithm here is based on the infix to RPN algorithm given in
	 * http://montcs.bloomu.edu/~bobmon/Information/RPN/infix2rpn.shtml
	 * It's essentially the same as Dijkstra's shunting yard algorithm.
	 */
	function doExpression( $expr ) {
		$operands = array();
		$operators = array();
 
		# Unescape inequality operators
		$expr = strtr( $expr, array( '&lt;' => '<', '&gt;' => '>' ) );
 
		$p = 0;
		$end = strlen( $expr );
		$expecting = 'expression';
 
 
		while ( $p < $end ) {
			if ( count( $operands ) > $this->maxStackSize || count( $operators ) > $this->maxStackSize ) {
				throw new ExprError('stack_exhausted');
			}
			$char = $expr[$p];
			$char2 = substr( $expr, $p, 2 );
 
			// Mega if-elseif-else construct
			// Only binary operators fall through for processing at the bottom, the rest 
			// finish their processing and continue
 
			// First the unlimited length classes
 
			if ( false !== strpos( EXPR_WHITE_CLASS, $char ) ) {
				// Whitespace
				$p += strspn( $expr, EXPR_WHITE_CLASS, $p );
				continue;
			} elseif ( false !== strpos( EXPR_NUMBER_CLASS, $char ) ) {
				// Number
				if ( $expecting != 'expression' ) {
					throw new ExprError('unexpected_number');
				}
 
				// Find the rest of it
				$length = strspn( $expr, EXPR_NUMBER_CLASS, $p );
				// Convert it to float, silently removing double decimal points
				$operands[] = floatval( substr( $expr, $p, $length ) );
				$p += $length;
				$expecting = 'operator';
				continue;
			} elseif ( ctype_alpha( $char ) ) {
				// Word
				// Find the rest of it
				$remaining = substr( $expr, $p );
				if ( !preg_match( '/^[A-Za-z]*/', $remaining, $matches ) ) {
					// This should be unreachable
					throw new ExprError('preg_match_failure');
				}
				$word = strtolower( $matches[0] );
				$p += strlen( $word );
 
				// Interpret the word
				if ( !isset( $this->words[$word] ) ){
					throw new ExprError('unrecognised_word', $word);
				}
				$op = $this->words[$word];
				// Unary operator
				switch($op){
				case EXPR_NOT:
				case EXPR_CEIL: 
 				case EXPR_FLOOR: 
 				case EXPR_ABS: 
 				case EXPR_SQRT: 
					if ( $expecting != 'expression' ) {
						throw new ExprError('unexpected_operator', $word);
					}
					$operators[] = $op;
					continue 2;
				}
				// Binary operator, fall through
				$name = $word;
			}
 
			// Next the two-character operators
 
			elseif ( $char2 == '<=' ) {
				$name = $char2;
				$op = EXPR_LESSEQ;
				$p += 2;
			} elseif ( $char2 == '>=' ) {
				$name = $char2;
				$op = EXPR_GREATEREQ;
				$p += 2;
			} elseif ( $char2 == '<>' || $char2 == '!=' ) {
				$name = $char2;
				$op = EXPR_NOTEQ;
				$p += 2;
			}
 
			// Finally the single-character operators
 
			elseif ( $char == '+' ) {
				++$p;
				if ( $expecting == 'expression' ) {
					// Unary plus
					$operators[] = EXPR_POSITIVE;
					continue;
				} else {
					// Binary plus
					$op = EXPR_PLUS;
				}
			} elseif ( $char == '-' ) {
				++$p;
				if ( $expecting == 'expression' ) {
					// Unary minus
					$operators[] = EXPR_NEGATIVE;
					continue;
				} else {
					// Binary minus
					$op = EXPR_MINUS;
				}
			} elseif ( $char == '*' ) {
				$name = $char;
				$op = EXPR_TIMES;
				++$p;
			} elseif ( $char == '/' ) {
				$name = $char;
				$op = EXPR_DIVIDE;
				++$p;
			} elseif ( $char == '(' )  {
				if ( $expecting == 'operator' ) {
					throw new ExprError('unexpected_operator', '(');
				}
				$operators[] = EXPR_OPEN;
				++$p;
				continue;
			} elseif ( $char == ')' ) {
				$lastOp = end( $operators );
				while ( $lastOp && $lastOp != EXPR_OPEN ) {
					$this->doOperation( $lastOp, $operands );
					array_pop( $operators );
					$lastOp = end( $operators );
				}
				if ( $lastOp ) {
					array_pop( $operators );
				} else {
					throw new ExprError('unexpected_closing_bracket');
				}
				$expecting = 'operator';
				++$p;
				continue;
			} elseif ( $char == '=' ) {
				$name = $char;
				$op = EXPR_EQUALITY;
				++$p;
			} elseif ( $char == '<' ) {
				$name = $char;
				$op = EXPR_LESS;
				++$p;
			} elseif ( $char == '>' ) {
				$name = $char;
				$op = EXPR_GREATER;
				++$p;
			} elseif ( $char == '^' ) { 
 				$name = $char; 
 				$op = EXPR_POW; 
 				++$p; 
			} else {
				throw new ExprError('unrecognised_punctuation', $char);
			}
 
			// Binary operator processing
			if ( $expecting == 'expression' ) {
				throw new ExprError('unexpected_operator', $name);
			}
 
			// Shunting yard magic
			$lastOp = end( $operators );
			while ( $lastOp && $this->precedence[$op] <= $this->precedence[$lastOp] ) {
				$this->doOperation( $lastOp, $operands );
				array_pop( $operators );
				$lastOp = end( $operators );
			}
			$operators[] = $op;
			$expecting = 'expression';
		}
 
		// Finish off the operator array
		while ( $op = array_pop( $operators ) ) {
			if ( $op == EXPR_OPEN ) {
				throw new ExprError('unclosed_bracket');
			}
			$this->doOperation( $op, $operands );
		}
 
		return implode( "<br />\n", $operands );
	}
 
	function doOperation( $op, &$stack ) {
		switch ( $op ) {
			case EXPR_NEGATIVE:
				if ( count( $stack ) < 1 ) throw new ExprError('missing_operand', $this->names[$op]);
				$arg = array_pop( $stack );
				$stack[] = -$arg;
				break;
			case EXPR_POSITIVE:
				if ( count( $stack ) < 1 ) throw new ExprError('missing_operand', $this->names[$op]);
				break;
			case EXPR_TIMES:
				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]);
				$right = array_pop( $stack );
				$left = array_pop( $stack );
				$stack[] = $left * $right;
					break;
			case EXPR_DIVIDE:
				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]);
				$right = array_pop( $stack );
				$left = array_pop( $stack );
				if ( $right == 0 ) throw new ExprError('division_by_zero', $this->names[$op]);
				$stack[] = $left / $right;
				break;
			case EXPR_MOD:
				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]);
				$right = array_pop( $stack );
				$left = array_pop( $stack );
				if ( $right == 0 ) throw new ExprError('division_by_zero', $this->names[$op]);
				$stack[] = $left % $right;
				break;
			case EXPR_PLUS:
				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]);
				$right = array_pop( $stack );
				$left = array_pop( $stack );
				$stack[] = $left + $right;
				break;
			case EXPR_MINUS:
				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]);
				$right = array_pop( $stack );
				$left = array_pop( $stack );
				$stack[] = $left - $right;
				break;
			case EXPR_AND:
				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]);
				$right = array_pop( $stack );
				$left = array_pop( $stack );
				$stack[] = ( $left && $right ) ? 1 : 0;
				break;
			case EXPR_OR:
				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]);
				$right = array_pop( $stack );
				$left = array_pop( $stack );
				$stack[] = ( $left || $right ) ? 1 : 0;
				break;
			case EXPR_EQUALITY:
				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]);
				$right = array_pop( $stack );
				$left = array_pop( $stack );
				$stack[] = ( $left == $right ) ? 1 : 0;
				break;
			case EXPR_NOT:
				if ( count( $stack ) < 1 ) throw new ExprError('missing_operand', $this->names[$op]);
				$arg = array_pop( $stack );
				$stack[] = (!$arg) ? 1 : 0;
				break;
			case EXPR_ROUND:
				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]);
				$digits = intval( array_pop( $stack ) );
				$value = array_pop( $stack );
				$stack[] = round( $value, $digits );
				break;
			case EXPR_LESS:
				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]);
				$right = array_pop( $stack );
				$left = array_pop( $stack );
				$stack[] = ( $left < $right ) ? 1 : 0;
				break;
			case EXPR_GREATER:
				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]);
				$right = array_pop( $stack );
				$left = array_pop( $stack );
				$stack[] = ( $left > $right ) ? 1 : 0;
				break;
			case EXPR_LESSEQ:
				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]);
				$right = array_pop( $stack );
				$left = array_pop( $stack );
				$stack[] = ( $left <= $right ) ? 1 : 0;
				break;
			case EXPR_GREATEREQ:
				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]);
				$right = array_pop( $stack );
				$left = array_pop( $stack );
				$stack[] = ( $left >= $right ) ? 1 : 0;
				break;
			case EXPR_NOTEQ:
				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]);
				$right = array_pop( $stack );
				$left = array_pop( $stack );
				$stack[] = ( $left != $right ) ? 1 : 0;
				break;
			case EXPR_ABS: 
 				if ( count( $stack ) < 1 ) throw new ExprError('missing_operand', $this->names[$op]); 
 				$arg = array_pop( $stack ); 
 				$stack[] = abs($arg); 
 				break; 
 			case EXPR_CEIL: 
 				if ( count( $stack ) < 1 ) throw new ExprError('missing_operand', $this->names[$op]); 
 				$arg = array_pop( $stack ); 
 				$stack[] = ceil($arg); 
 				break; 
 			case EXPR_FLOOR: 
 				if ( count( $stack ) < 1 ) throw new ExprError('missing_operand', $this->names[$op]); 
 				$arg = array_pop( $stack ); 
 				$stack[] = floor($arg); 
 				break; 
 			case EXPR_E: 
 				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]); 
 				$right = array_pop( $stack ); 
 				$left = array_pop( $stack ); 
 				// can never be NaN 
 				$stack[] = $left * pow(10,$right); 
 				break; 
 			case EXPR_POW: 
 				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]); 
 				$right = array_pop( $stack ); 
 				$left = array_pop( $stack ); 
 				$result = pow($left,$right); 
 				if (is_nan($result)) throw new ExprError('not_a_number', $this->names[$op]); 
 				$stack[] = $result; 
 				break; 
 			case EXPR_FLOAT_MOD: 
 				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]); 
 				$right = array_pop( $stack ); 
 				$left = array_pop( $stack ); 
 				if ( $right == 0 ) throw new ExprError('division_by_zero', $this->names[$op]); 
 				$stack[] = fmod($left,$right); 
 				break; 
 			case EXPR_SQRT: 
 				if ( count( $stack ) < 1 ) throw new ExprError('missing_operand', $this->names[$op]); 
 				$arg = array_pop( $stack ); 
 				$result = sqrt($arg); 
 				if (is_nan($result)) throw new ExprError('not_a_number', $this->names[$op]); 
 				$stack[] = $result; 
 				break; 
 			case EXPR_INT_DIVIDE: 
 				if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]); 
 				$right = array_pop( $stack ); 
 				$left = array_pop( $stack ); 
 				if ( $right == 0 ) throw new ExprError('division_by_zero', $this->names[$op]); 
 				$stack[] = (int)($left / $right); 
 				break; 
			default:
				// Should be impossible to reach here.
				throw new ExprError('unknown_error');
		}
	}
}

ParserFunctions.php (extended)[edit | edit source]

<?php
 
if ( !defined( 'MEDIAWIKI' ) ) {
	die( 'This file is a MediaWiki extension, it is not a valid entry point' );
}
 
$wgExtensionFunctions[] = 'wfSetupParserFunctions';
$wgExtensionCredits['parserhook'][] = array(
	'name' => 'ParserFunctions (extended)',
	'url' => 'http://meta.wikimedia.org/wiki/ParserFunctions',
	'author' => 'Tim Starling, Carl Fürstenberg (AzaToth)',
	'description' => 'Enhance parser with mathematical and logical functions',
);
 
$wgHooks['LanguageGetMagic'][]       = 'wfParserFunctionsLanguageGetMagic';
 
class ExtParserFunctions {
	var $mExprParser;
	var $mTimeCache = array();
	var $mTimeChars = 0;
	var $mMaxTimeChars = 6000; # ~10 seconds

	function clearState() {
		$this->mTimeChars = 0;
		return true;
	}
 
	function &getExprParser() {
		if ( !isset( $this->mExpr ) ) {
			if ( !class_exists( 'ExprParser' ) ) {
				require( dirname( __FILE__ ) . '/Expr.php' );
				ExprParser::addMessages();
			}
			$this->mExprParser = new ExprParser;
		}
		return $this->mExprParser;
	}
 
	function expr( &$parser, $expr = '' ) {
		try {
			return $this->getExprParser()->doExpression( $expr );
		} catch(ExprError $e) {
			return $e->getMessage();
		}
	}
 
    function maxHook(&$parser) { 
                try {
 		$args = func_get_args(); 
         array_shift( $args ); 
 
         $exprParser = $this->getExprParser(); 
         foreach($args as $expr){ 
             $res = $exprParser->doExpression($expr); 
                 $result[] = $res; 
         }
         return max($result); 
         } catch(ExprError $e) {
			return $e->getMessage();
	 }
     } 
 
   function minHook(&$parser) { 
                try {
 		$args = func_get_args(); 
         array_shift( $args ); 
 
         $exprParser = $this->getExprParser(); 
         foreach($args as $expr){ 
             $res = $exprParser->doExpression($expr); 
                 $result[] = $res; 
         }
         return min($result); 
         } 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 ifHook( &$parser, $test = '', $then = '', $else = '' ) {
		if ( $test !== '' ) {
			return $then;
		} else {
			return $else;
		}
	}
 
	function ifeq( &$parser, $left = '', $right = '', $then = '', $else = '' ) {
		if ( $left == $right ) {
			return $then;
		} else {
			return $else;
		}
	}
 
	function switchHook( &$parser /*,...*/ ) {
		$args = func_get_args();
		array_shift( $args );
		$value = trim(array_shift($args));
		$found = false;
		$parts = null;
		$default = null;
		foreach( $args as $arg ) {
			$parts = array_map( 'trim', explode( '=', $arg, 2 ) );
			if ( count( $parts ) == 2 ) {
				if ( $found || $parts[0] == $value ) {
					return $parts[1];
				} else {
					$mwDefault =& MagicWord::get( 'default' );
					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] == $value ) {
					$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 '';
		}
	}
 
	/**
	 * 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->mTitle->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
					return wfMsgForContent( 'pfunc_rel2abs_invalid_depth', $fullPath );
				}
				// 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 ifexist( &$parser, $title = '', $then = '', $else = '' ) {
		$title = Title::newFromText( $title );
		return is_object( $title ) && $title->exists() ? $then : $else;
	}
 
	function time( &$parser, $format = '', $date = '' ) {
		global $wgContLang;
		if ( isset( $this->mTimeCache[$format][$date] ) ) {
			return $this->mTimeCache[$format][$date];
		}
 
		if ( $date !== '' ) {
			$unix = @strtotime( $date );
		} else {
			$unix = time();
		}
 
		if ( $unix == -1 || $unix == false ) {
			$result = wfMsgForContent( 'pfunc_time_error' );
		} else {
			$this->mTimeChars += strlen( $format );
			if ( $this->mTimeChars > $this->mMaxTimeChars ) {
				return wfMsgForContent( 'pfunc_time_too_long' );
			} else {
				$ts = wfTimestamp( TS_MW, $unix );
				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] = $result;
		return $result;
	}
}
 
function wfSetupParserFunctions() {
	global $wgParser, $wgMessageCache, $wgExtParserFunctions, $wgMessageCache, $wgHooks;
 
	$wgExtParserFunctions = new ExtParserFunctions;
 
	$wgParser->setFunctionHook( 'expr', array( &$wgExtParserFunctions, 'expr' ) );
	$wgParser->setFunctionHook( 'if', array( &$wgExtParserFunctions, 'ifHook' ) );
	$wgParser->setFunctionHook( 'ifeq', array( &$wgExtParserFunctions, 'ifeq' ) );
	$wgParser->setFunctionHook( 'ifexpr', array( &$wgExtParserFunctions, 'ifexpr' ) );
	$wgParser->setFunctionHook( 'switch', array( &$wgExtParserFunctions, 'switchHook' ) );
	$wgParser->setFunctionHook( 'ifexist', array( &$wgExtParserFunctions, 'ifexist' ) );
	$wgParser->setFunctionHook( 'time', array( &$wgExtParserFunctions, 'time' ) );
	$wgParser->setFunctionHook( 'max', array( &$wgExtParserFunctions, 'maxHook' ) );	 
 	$wgParser->setFunctionHook( 'min', array( &$wgExtParserFunctions, 'minHook' ) );	 
 	$wgParser->setFunctionHook( 'rel2abs', array( &$wgExtParserFunctions, 'rel2abs' ) );
 
	$wgMessageCache->addMessage( 'pfunc_time_error', "Error: invalid time" );
	$wgMessageCache->addMessage( 'pfunc_time_too_long', "Error: too many #time calls" );
	$wgMessageCache->addMessage( 'pfunc_rel2abs_invalid_depth', "Error: Invalid depth in path: \"$1\" (tried to access a node above the root node)" );
 
	$wgHooks['ParserClearState'][] = array( &$wgExtParserFunctions, 'clearState' );
}
 
function wfParserFunctionsLanguageGetMagic( &$magicWords, $langCode ) {
	switch ( $langCode ) {
		case 'he':
			$magicWords['expr']    = array( 0, 'חשב',         'expr' );
			$magicWords['if']      = array( 0, 'תנאי',        'if' );
			$magicWords['ifeq']    = array( 0, 'שווה',        'ifeq' );
			$magicWords['ifexpr']  = array( 0, 'חשב תנאי',    'ifexpr' );
			$magicWords['switch']  = array( 0, 'בחר',         'switch' );
			$magicWords['default'] = array( 0, '#ברירת מחדל', '#default' );
			$magicWords['ifexist'] = array( 0, 'קיים',         'ifexist' );
			$magicWords['time']    = array( 0, 'זמן',          'time' );
			$magicWords['rel2abs'] = array( 0, 'יחסי למוחלט',  'rel2abs' );
			break;
		default:
			$magicWords['expr']    = array( 0, 'expr' );
			$magicWords['if']      = array( 0, 'if' );
			$magicWords['ifeq']    = array( 0, 'ifeq' );
			$magicWords['ifexpr']  = array( 0, 'ifexpr' );
			$magicWords['switch']  = array( 0, 'switch' );
			$magicWords['default'] = array( 0, '#default' );
			$magicWords['ifexist'] = array( 0, 'ifexist' );
			$magicWords['time']    = array( 0, 'time' );
			$magicWords['max']     = array( 0, 'max' ); 
 			$magicWords['min']     = array( 0, 'min' ); 
			$magicWords['rel2abs'] = array( 0, 'rel2abs' );
	}
	return true;
}

See also[edit | edit source]