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;
-}
-