define( 'OT_WIKI', 2 );
define( 'OT_MSG' , 3 );
+# Flags for setFunctionHook
+define( 'SFH_NO_HASH', 1 );
+
# string parameter for extractTags which will cause it
# to strip HTML comments in addition to regular
# <XML>-style tags. This should not be anything we
* @private
*/
# Persistent:
- var $mTagHooks, $mFunctionHooks;
+ var $mTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables;
# Cleared with clearState():
var $mOutput, $mAutonumber, $mDTopen, $mStripState = array();
- var $mVariables, $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
+ var $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
var $mInterwikiLinkHolders, $mLinkHolders, $mUniqPrefix;
var $mTemplates, // cache of already loaded templates, avoids
// multiple SQL queries for the same string
function Parser() {
$this->mTagHooks = array();
$this->mFunctionHooks = array();
- $this->clearState();
- $this->setHook( 'pre', array( $this, 'renderPreTag' ) );
+ $this->mFunctionSynonyms = array( 0 => array(), 1 => array() );
+ $this->mFirstCall = true;
}
+ /**
+ * Do various kinds of initialisation on the first call of the parser
+ */
+ function firstCallInit() {
+ if ( !$this->mFirstCall ) {
+ return;
+ }
+
+ wfProfileIn( __METHOD__ );
+ global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions;
+
+ $this->setHook( 'pre', array( $this, 'renderPreTag' ) );
+
+ $this->setFunctionHook( 'ns', array( 'CoreParserFunctions', 'ns' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'urlencode', array( 'CoreParserFunctions', 'urlencode' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'lcfirst', array( 'CoreParserFunctions', 'lcfirst' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'ucfirst', array( 'CoreParserFunctions', 'ucfirst' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'lc', array( 'CoreParserFunctions', 'lc' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'uc', array( 'CoreParserFunctions', 'uc' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'localurl', array( 'CoreParserFunctions', 'localurl' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'localurle', array( 'CoreParserFunctions', 'localurle' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'fullurl', array( 'CoreParserFunctions', 'fullurl' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'fullurle', array( 'CoreParserFunctions', 'fullurle' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'formatnum', array( 'CoreParserFunctions', 'formatnum' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'grammar', array( 'CoreParserFunctions', 'grammar' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'plural', array( 'CoreParserFunctions', 'plural' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberofpages', array( 'CoreParserFunctions', 'numberofpages' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberofusers', array( 'CoreParserFunctions', 'numberofusers' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberofarticles', array( 'CoreParserFunctions', 'numberofarticles' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberoffiles', array( 'CoreParserFunctions', 'numberoffiles' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberofadmins', array( 'CoreParserFunctions', 'numberofadmins' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'language', array( 'CoreParserFunctions', 'language' ), SFH_NO_HASH );
+
+ if ( $wgAllowDisplayTitle ) {
+ $this->setFunctionHook( 'displaytitle', array( 'CoreParserFunctions', 'displaytitle' ), SFH_NO_HASH );
+ }
+ if ( $wgAllowSlowParserFunctions ) {
+ $this->setFunctionHook( 'pagesinnamespace', array( 'CoreParserFunctions', 'pagesinnamespace' ), SFH_NO_HASH );
+ }
+
+ $this->initialiseVariables();
+
+ $this->mFirstCall = false;
+ wfProfileOut( __METHOD__ );
+ }
+
/**
* Clear Parser state
*
* @private
*/
function clearState() {
+ if ( $this->mFirstCall ) {
+ $this->firstCallInit();
+ }
$this->mOutput = new ParserOutput;
$this->mAutonumber = 0;
$this->mLastSection = '';
$this->mDTopen = false;
- $this->mVariables = false;
$this->mIncludeCount = array();
$this->mStripState = array();
$this->mArgStack = array();
$this->mShowToc = true;
$this->mForceTocPosition = false;
-
+
wfRunHooks( 'ParserClearState', array( &$this ) );
}
$this->mOptions = $options;
$this->mTitle =& $title;
- $this->mRevisionId = $revid;
+ $oldRevisionId = $this->mRevisionId;
+ if( $revid !== null ) {
+ $this->mRevisionId = $revid;
+ }
$this->mOutputType = OT_HTML;
//$text = $this->strip( $text, $this->mStripState );
} else {
# attempt to sanitize at least some nesting problems
# (bug #2702 and quite a few others)
- $tidyregs = array(
- # ''Something [http://www.cool.com cool''] -->
+ $tidyregs = array(
+ # ''Something [http://www.cool.com cool''] -->
# <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
'/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
'\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
'\\1\\3<div\\5>\\6</div>\\8\\9',
# remove empty italic or bold tag pairs, some
# introduced by rules above
- '/<([bi])><\/\\1>/' => ''
+ '/<([bi])><\/\\1>/' => '',
);
- $text = preg_replace(
+ $text = preg_replace(
array_keys( $tidyregs ),
array_values( $tidyregs ),
$text );
wfRunHooks( 'ParserAfterTidy', array( &$this, &$text ) );
$this->mOutput->setText( $text );
+ $this->mRevisionId = $oldRevisionId;
wfProfileOut( $fname );
return $this->mOutput;
function &getTitle() { return $this->mTitle; }
function getOptions() { return $this->mOptions; }
+ function getFunctionLang() {
+ global $wgLang, $wgContLang;
+ return $this->mOptions->getInterfaceMessage() ? $wgLang : $wgContLang;
+ }
+
/**
* Replaces all occurrences of HTML-style comments and the given tags
* in the text with a random marker and returns teh next text. The output
$state[$element][$marker] = $output;
}
}
-
+
# Unstrip comments unless explicitly told otherwise.
# (The comments are always stripped prior to this point, so as to
# not invoke any extension tags / parser hooks contained within
}
$after = substr ( $x , 1 ) ;
if ( $fc == '!' ) $after = str_replace ( '!!' , '||' , $after ) ;
-
+
// Split up multiple cells on the same line.
// FIXME: This can result in improper nesting of tags processed
// by earlier parser steps, but should avoid splitting up eg
// attribute values containing literal "||".
$after = wfExplodeMarkup( '||', $after );
-
+
$t[$k] = '' ;
# Loop through each table cell
$text = strtr( $text, array( '<onlyinclude>' => '' , '</onlyinclude>' => '' ) );
$text = strtr( $text, array( '<noinclude>' => '', '</noinclude>' => '') );
$text = preg_replace( '/<includeonly>.*?<\/includeonly>/s', '', $text );
-
+
$text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ) );
$text = $this->replaceVariables( $text, $args );
}
$text = $wgContLang->markNoConversion($text);
-
- # Normalize any HTML entities in input. They will be
- # re-escaped by makeExternalLink().
- $url = Sanitizer::decodeCharReferences( $url );
- # Escape any control characters introduced by the above step
- $url = preg_replace( '/[\][<>"\\x00-\\x20\\x7F]/e', "urlencode('\\0')", $url );
+ $url = Sanitizer::cleanUrl( $url );
# Process the trail (i.e. everything after this link up until start of the next link),
# replacing any non-bracketed links
$url = substr( $url, 0, -$numSepChars );
}
- # Normalize any HTML entities in input. They will be
- # re-escaped by makeExternalLink() or maybeMakeExternalImage()
- $url = Sanitizer::decodeCharReferences( $url );
-
- # Escape any control characters introduced by the above step
- $url = preg_replace( '/[\][<>"\\x00-\\x20\\x7F]/e', "urlencode('\\0')", $url );
+ $url = Sanitizer::cleanUrl( $url );
# Is this an external image?
$text = $this->maybeMakeExternalImage( $url );
* the URL differently; as a workaround, just use the output for
* statistical records, not for actual linking/output.
*/
- function replaceUnusualEscapes( $url ) {
+ static function replaceUnusualEscapes( $url ) {
return preg_replace_callback( '/%[0-9A-Fa-f]{2}/',
array( 'Parser', 'replaceUnusualEscapesCallback' ), $url );
}
* @static
* @private
*/
- function replaceUnusualEscapesCallback( $matches ) {
+ private static function replaceUnusualEscapesCallback( $matches ) {
$char = urldecode( $matches[0] );
$ord = ord( $char );
// Is it an unsafe or HTTP reserved character according to RFC 1738?
$m[3] = $n[1];
}
# fix up urlencoded title texts
- if(preg_match('/%/', $m[1] ))
+ if(preg_match('/%/', $m[1] ))
# Should anchors '#' also be rejected?
$m[1] = str_replace( array('<', '>'), array('<', '>'), urldecode($m[1]) );
$trail = $m[3];
wfProfileOut( "$fname-image" );
continue;
+ } else {
+ # We still need to record the image's presence on the page
+ $this->mOutput->addImage( $nt->getDBkey() );
}
wfProfileOut( "$fname-image" );
$s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix );
continue;
} elseif( $ns == NS_IMAGE ) {
- $img = Image::newFromTitle( $nt );
+ $img = new Image( $nt );
if( $img->exists() ) {
// Force a blue link if the file exists; may be a remote
// upload on the shared repository, and we want to see its
function findColonNoLinks($str, &$before, &$after) {
$fname = 'Parser::findColonNoLinks';
wfProfileIn( $fname );
-
+
$pos = strpos( $str, ':' );
if( $pos === false ) {
// Nothing to find!
wfProfileOut( $fname );
return false;
}
-
+
$lt = strpos( $str, '<' );
if( $lt === false || $lt > $pos ) {
// Easy; no tag nesting to worry about
wfProfileOut( $fname );
return $pos;
}
-
+
// Ugly state machine to walk through avoiding tags.
$state = MW_COLON_STATE_TEXT;
$stack = 0;
$len = strlen( $str );
for( $i = 0; $i < $len; $i++ ) {
$c = $str{$i};
-
+
switch( $state ) {
// (Using the number is a performance hack for common cases)
case 0: // MW_COLON_STATE_TEXT:
wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) );
switch ( $index ) {
- case MAG_CURRENTMONTH:
+ case 'currentmonth':
return $varCache[$index] = $wgContLang->formatNum( date( 'm', $ts ) );
- case MAG_CURRENTMONTHNAME:
+ case 'currentmonthname':
return $varCache[$index] = $wgContLang->getMonthName( date( 'n', $ts ) );
- case MAG_CURRENTMONTHNAMEGEN:
+ case 'currentmonthnamegen':
return $varCache[$index] = $wgContLang->getMonthNameGen( date( 'n', $ts ) );
- case MAG_CURRENTMONTHABBREV:
+ case 'currentmonthabbrev':
return $varCache[$index] = $wgContLang->getMonthAbbreviation( date( 'n', $ts ) );
- case MAG_CURRENTDAY:
+ case 'currentday':
return $varCache[$index] = $wgContLang->formatNum( date( 'j', $ts ) );
- case MAG_CURRENTDAY2:
+ case 'currentday2':
return $varCache[$index] = $wgContLang->formatNum( date( 'd', $ts ) );
- case MAG_PAGENAME:
+ case 'pagename':
return $this->mTitle->getText();
- case MAG_PAGENAMEE:
+ case 'pagenamee':
return $this->mTitle->getPartialURL();
- case MAG_FULLPAGENAME:
+ case 'fullpagename':
return $this->mTitle->getPrefixedText();
- case MAG_FULLPAGENAMEE:
+ case 'fullpagenamee':
return $this->mTitle->getPrefixedURL();
- case MAG_SUBPAGENAME:
+ case 'subpagename':
return $this->mTitle->getSubpageText();
- case MAG_SUBPAGENAMEE:
+ case 'subpagenamee':
return $this->mTitle->getSubpageUrlForm();
- case MAG_BASEPAGENAME:
+ case 'basepagename':
return $this->mTitle->getBaseText();
- case MAG_BASEPAGENAMEE:
+ case 'basepagenamee':
return wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getBaseText() ) );
- case MAG_TALKPAGENAME:
+ case 'talkpagename':
if( $this->mTitle->canTalk() ) {
$talkPage = $this->mTitle->getTalkPage();
return $talkPage->getPrefixedText();
} else {
return '';
}
- case MAG_TALKPAGENAMEE:
+ case 'talkpagenamee':
if( $this->mTitle->canTalk() ) {
$talkPage = $this->mTitle->getTalkPage();
return $talkPage->getPrefixedUrl();
} else {
return '';
}
- case MAG_SUBJECTPAGENAME:
+ case 'subjectpagename':
$subjPage = $this->mTitle->getSubjectPage();
return $subjPage->getPrefixedText();
- case MAG_SUBJECTPAGENAMEE:
+ case 'subjectpagenamee':
$subjPage = $this->mTitle->getSubjectPage();
return $subjPage->getPrefixedUrl();
- case MAG_REVISIONID:
+ case 'revisionid':
return $this->mRevisionId;
- case MAG_NAMESPACE:
+ case 'namespace':
return str_replace('_',' ',$wgContLang->getNsText( $this->mTitle->getNamespace() ) );
- case MAG_NAMESPACEE:
+ case 'namespacee':
return wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
- case MAG_TALKSPACE:
+ case 'talkspace':
return $this->mTitle->canTalk() ? str_replace('_',' ',$this->mTitle->getTalkNsText()) : '';
- case MAG_TALKSPACEE:
+ case 'talkspacee':
return $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
- case MAG_SUBJECTSPACE:
+ case 'subjectspace':
return $this->mTitle->getSubjectNsText();
- case MAG_SUBJECTSPACEE:
+ case 'subjectspacee':
return( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
- case MAG_CURRENTDAYNAME:
+ case 'currentdayname':
return $varCache[$index] = $wgContLang->getWeekdayName( date( 'w', $ts ) + 1 );
- case MAG_CURRENTYEAR:
+ case 'currentyear':
return $varCache[$index] = $wgContLang->formatNum( date( 'Y', $ts ), true );
- case MAG_CURRENTTIME:
+ case 'currenttime':
return $varCache[$index] = $wgContLang->time( wfTimestamp( TS_MW, $ts ), false, false );
- case MAG_CURRENTWEEK:
+ case 'currentweek':
// @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
// int to remove the padding
return $varCache[$index] = $wgContLang->formatNum( (int)date( 'W', $ts ) );
- case MAG_CURRENTDOW:
+ case 'currentdow':
return $varCache[$index] = $wgContLang->formatNum( date( 'w', $ts ) );
- case MAG_NUMBEROFARTICLES:
+ case 'numberofarticles':
return $varCache[$index] = $wgContLang->formatNum( wfNumberOfArticles() );
- case MAG_NUMBEROFFILES:
+ case 'numberoffiles':
return $varCache[$index] = $wgContLang->formatNum( wfNumberOfFiles() );
- case MAG_NUMBEROFUSERS:
+ case 'numberofusers':
return $varCache[$index] = $wgContLang->formatNum( wfNumberOfUsers() );
- case MAG_NUMBEROFPAGES:
+ case 'numberofpages':
return $varCache[$index] = $wgContLang->formatNum( wfNumberOfPages() );
- case MAG_NUMBEROFADMINS:
+ case 'numberofadmins':
return $varCache[$index] = $wgContLang->formatNum( wfNumberOfAdmins() );
- case MAG_CURRENTTIMESTAMP:
+ case 'currenttimestamp':
return $varCache[$index] = wfTimestampNow();
- case MAG_CURRENTVERSION:
+ case 'currentversion':
global $wgVersion;
return $wgVersion;
- case MAG_SITENAME:
+ case 'sitename':
return $wgSitename;
- case MAG_SERVER:
+ case 'server':
return $wgServer;
- case MAG_SERVERNAME:
+ case 'servername':
return $wgServerName;
- case MAG_SCRIPTPATH:
+ case 'scriptpath':
return $wgScriptPath;
- case MAG_DIRECTIONMARK:
+ case 'directionmark':
return $wgContLang->getDirMark();
- case MAG_CONTENTLANGUAGE:
+ case 'contentlanguage':
global $wgContLanguageCode;
return $wgContLanguageCode;
default:
function initialiseVariables() {
$fname = 'Parser::initialiseVariables';
wfProfileIn( $fname );
- global $wgVariableIDs;
+ $variableIDs = MagicWord::getVariableIDs();
+
$this->mVariables = array();
- foreach ( $wgVariableIDs as $id ) {
+ foreach ( $variableIDs as $id ) {
$mw =& MagicWord::get( $id );
$mw->addToArray( $this->mVariables, $id );
}
* @private
*/
function replace_callback ($text, $callbacks) {
+ wfProfileIn( __METHOD__ . '-self' );
$openingBraceStack = array(); # this array will hold a stack of parentheses which are not closed yet
$lastOpeningBrace = -1; # last not closed parentheses
'lineStart' => (($pieceStart > 0) && ($text[$pieceStart-1] == "\n")),
);
# finally we can call a user callback and replace piece of text
+ wfProfileOut( __METHOD__ . '-self' );
$replaceWith = call_user_func( $matchingCallback, $cbArgs );
+ wfProfileIn( __METHOD__ . '-self' );
$text = substr($text, 0, $pieceStart) . $replaceWith . substr($text, $pieceEnd);
$i = $pieceStart + strlen($replaceWith) - 1;
}
}
}
+ wfProfileOut( __METHOD__ . '-self' );
return $text;
}
wfProfileOut( $fname );
return $text;
}
-
+
/**
* Replace magic variables
* @private
$fname = 'Parser::variableSubstitution';
$varname = $matches[1];
wfProfileIn( $fname );
- if ( !$this->mVariables ) {
- $this->initialiseVariables();
- }
$skip = false;
if ( $this->mOutputType == OT_WIKI ) {
# Do only magic variables prefixed by SUBST
- $mwSubst =& MagicWord::get( MAG_SUBST );
+ $mwSubst =& MagicWord::get( 'subst' );
if (!$mwSubst->matchStartAndRemove( $varname ))
$skip = true;
# Note that if we don't substitute the variable below,
# SUBST
if ( !$found ) {
- $mwSubst =& MagicWord::get( MAG_SUBST );
+ $mwSubst =& MagicWord::get( 'subst' );
if ( $mwSubst->matchStartAndRemove( $part1 ) xor ($this->mOutputType == OT_WIKI) ) {
# One of two possibilities is true:
# 1) Found SUBST but not in the PST phase
# MSG, MSGNW, INT and RAW
if ( !$found ) {
# Check for MSGNW:
- $mwMsgnw =& MagicWord::get( MAG_MSGNW );
+ $mwMsgnw =& MagicWord::get( 'msgnw' );
if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
$nowiki = true;
} else {
# Remove obsolete MSG:
- $mwMsg =& MagicWord::get( MAG_MSG );
+ $mwMsg =& MagicWord::get( 'msg' );
$mwMsg->matchStartAndRemove( $part1 );
}
# Check for RAW:
- $mwRaw =& MagicWord::get( MAG_RAW );
+ $mwRaw =& MagicWord::get( 'raw' );
if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
$forceRawInterwiki = true;
}
# Check if it is an internal message
- $mwInt =& MagicWord::get( MAG_INT );
+ $mwInt =& MagicWord::get( 'int' );
if ( $mwInt->matchStartAndRemove( $part1 ) ) {
if ( $this->incrementIncludeCount( 'int:'.$part1 ) ) {
$text = $linestart . wfMsgReal( $part1, $args, true );
}
}
- # NS
+ # Parser functions
if ( !$found ) {
- # Check for NS: (namespace expansion)
- $mwNs = MagicWord::get( MAG_NS );
- if ( $mwNs->matchStartAndRemove( $part1 ) ) {
- if ( intval( $part1 ) || $part1 == "0" ) {
- $text = $linestart . $wgContLang->getNsText( intval( $part1 ) );
- $found = true;
- } else {
- $param = str_replace( ' ', '_', strtolower( $part1 ) );
- $index = Namespace::getCanonicalIndex( strtolower( $param ) );
- if ( !is_null( $index ) ) {
- $text = $linestart . $wgContLang->getNsText( $index );
- $found = true;
- }
- }
- }
- }
-
- # URLENCODE
- if( !$found ) {
- $urlencode =& MagicWord::get( MAG_URLENCODE );
- if( $urlencode->matchStartAndRemove( $part1 ) ) {
- $text = $linestart . urlencode( $part1 );
- $found = true;
- }
- }
-
- # LCFIRST, UCFIRST, LC and UC
- if ( !$found ) {
- $lcfirst =& MagicWord::get( MAG_LCFIRST );
- $ucfirst =& MagicWord::get( MAG_UCFIRST );
- $lc =& MagicWord::get( MAG_LC );
- $uc =& MagicWord::get( MAG_UC );
- if ( $lcfirst->matchStartAndRemove( $part1 ) ) {
- $text = $linestart . $wgContLang->lcfirst( $part1 );
- $found = true;
- } else if ( $ucfirst->matchStartAndRemove( $part1 ) ) {
- $text = $linestart . $wgContLang->ucfirst( $part1 );
- $found = true;
- } else if ( $lc->matchStartAndRemove( $part1 ) ) {
- $text = $linestart . $wgContLang->lc( $part1 );
- $found = true;
- } else if ( $uc->matchStartAndRemove( $part1 ) ) {
- $text = $linestart . $wgContLang->uc( $part1 );
- $found = true;
- }
- }
-
- # LOCALURL and FULLURL
- if ( !$found ) {
- $mwLocal =& MagicWord::get( MAG_LOCALURL );
- $mwLocalE =& MagicWord::get( MAG_LOCALURLE );
- $mwFull =& MagicWord::get( MAG_FULLURL );
- $mwFullE =& MagicWord::get( MAG_FULLURLE );
-
-
- if ( $mwLocal->matchStartAndRemove( $part1 ) ) {
- $func = 'getLocalURL';
- } elseif ( $mwLocalE->matchStartAndRemove( $part1 ) ) {
- $func = 'escapeLocalURL';
- } elseif ( $mwFull->matchStartAndRemove( $part1 ) ) {
- $func = 'getFullURL';
- } elseif ( $mwFullE->matchStartAndRemove( $part1 ) ) {
- $func = 'escapeFullURL';
- } else {
- $func = false;
- }
-
- if ( $func !== false ) {
- $title = Title::newFromText( $part1 );
- # Due to order of execution of a lot of bits, the values might be encoded
- # before arriving here; if that's true, then the title can't be created
- # and the variable will fail. If we can't get a decent title from the first
- # attempt, url-decode and try for a second.
- if( is_null( $title ) )
- $title = Title::newFromUrl( urldecode( $part1 ) );
- if ( !is_null( $title ) ) {
- if ( $argc > 0 ) {
- $text = $linestart . $title->$func( $args[0] );
- } else {
- $text = $linestart . $title->$func();
- }
- $found = true;
- }
- }
- }
-
- $lang = $this->mOptions->getInterfaceMessage() ? $wgLang : $wgContLang;
- if ( !$found && $argc == 1 ) {
- $mwGrammar =& MagicWord::get( MAG_FORMATNUM );
- if ( $mwGrammar->matchStartAndRemove( $part1 ) ) {
- $text = $linestart . $lang->formatNum( $args[0] );
- $found = true;
- }
- }
-
-
- # GRAMMAR
- if ( !$found && $argc == 1 ) {
- $mwGrammar =& MagicWord::get( MAG_GRAMMAR );
- if ( $mwGrammar->matchStartAndRemove( $part1 ) ) {
- $text = $linestart . $lang->convertGrammar( $args[0], $part1 );
- $found = true;
- }
- }
-
- # PLURAL
- if ( !$found && $argc >= 2 ) {
- $mwPluralForm =& MagicWord::get( MAG_PLURAL );
- if ( $mwPluralForm->matchStartAndRemove( $part1 ) ) {
- while ( count($args) < 5 ) { $args[] = $args[count($args)-1]; }
- $text = $linestart . $lang->convertPlural( $part1, $args[0], $args[1],
- $args[2], $args[3], $args[4]);
- $found = true;
- }
- }
-
- # DISPLAYTITLE
- if ( !$found && $argc == 1 && $wgAllowDisplayTitle ) {
- $mwDT =& MagicWord::get( MAG_DISPLAYTITLE );
- if ( $mwDT->matchStartAndRemove( $part1 ) ) {
-
- # Set title in parser output object
- $param = $args[0];
- $parserOptions = new ParserOptions;
- $local_parser = new Parser ();
- $t2 = $local_parser->parse ( $param, $this->mTitle, $parserOptions, false );
- $this->mOutput->mHTMLtitle = $t2->GetText();
-
- # Add subtitle
- $t = $this->mTitle->getPrefixedText();
- $this->mOutput->mSubtitle .= wfMsg('displaytitle', $t);
- $text = "" ;
- $found = true ;
- }
- }
-
- # NUMBEROFPAGES, NUMBEROFUSERS, NUMBEROFARTICLES, and NUMBEROFFILES
- if( !$found ) {
- $mwWordsToCheck = array( MAG_NUMBEROFPAGES => 'wfNumberOfPages',
- MAG_NUMBEROFUSERS => 'wfNumberOfUsers',
- MAG_NUMBEROFARTICLES => 'wfNumberOfArticles',
- MAG_NUMBEROFFILES => 'wfNumberOfFiles',
- MAG_NUMBEROFADMINS => 'wfNumberOfAdmins' );
- foreach( $mwWordsToCheck as $word => $func ) {
- $mwCurrentWord =& MagicWord::get( $word );
- if( $mwCurrentWord->matchStartAndRemove( $part1 ) ) {
- $mwRawSuffix =& MagicWord::get( MAG_RAWSUFFIX );
- if( isset( $args[0] ) && $mwRawSuffix->match( $args[0] ) ) {
- # Raw and unformatted
- $text = $linestart . call_user_func( $func );
- } else {
- # Formatted according to the content default
- $text = $linestart . $wgContLang->formatNum( call_user_func( $func ) );
- }
- $found = true;
- }
- }
- }
-
- # PAGESINNAMESPACE
- if( !$found ) {
- $mwPagesInNs =& MagicWord::get( MAG_PAGESINNAMESPACE );
- if( $mwPagesInNs->matchStartAndRemove( $part1 ) ) {
- $found = true;
- $count = wfPagesInNs( intval( $part1 ) );
- $mwRawSuffix =& MagicWord::get( MAG_RAWSUFFIX );
- if( isset( $args[0] ) && $mwRawSuffix->match( $args[0] ) ) {
- $text = $linestart . $count;
- } else {
- $text = $linestart . $wgContLang->formatNum( $count );
- }
- }
- }
-
- # #LANGUAGE:
- if( !$found ) {
- $mwLanguage =& MagicWord::get( MAG_LANGUAGE );
- if( $mwLanguage->matchStartAndRemove( $part1 ) ) {
- $lang = $wgContLang->getLanguageName( strtolower( $part1 ) );
- $text = $linestart . ( $lang != '' ? $lang : $part1 );
- $found = true;
- }
- }
-
- # Extensions
- if ( !$found && substr( $part1, 0, 1 ) == '#' ) {
+ wfProfileIn( __METHOD__ . '-pfunc' );
+
$colonPos = strpos( $part1, ':' );
if ( $colonPos !== false ) {
- $function = strtolower( substr( $part1, 1, $colonPos - 1 ) );
- if ( !isset( $this->mFunctionHooks[$function] ) ) {
- foreach ($this->mFunctionHooks as $key => $value) {
- if( is_int( $key ) ) {
- $mwExtension =& MagicWord::get( $key );
- if( $mwExtension->matchVariableStartToEnd( $function ) ) {
- $function = $key;
- break;
- }
- }
+ # Case sensitive functions
+ $function = substr( $part1, 0, $colonPos );
+ if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
+ $function = $this->mFunctionSynonyms[1][$function];
+ } else {
+ # Case insensitive functions
+ $function = strtolower( $function );
+ if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
+ $function = $this->mFunctionSynonyms[0][$function];
+ } else {
+ $function = false;
}
}
- if ( isset( $this->mFunctionHooks[$function] ) ) {
+ if ( $function ) {
$funcArgs = array_map( 'trim', $args );
$funcArgs = array_merge( array( &$this, trim( substr( $part1, $colonPos + 1 ) ) ), $funcArgs );
$result = call_user_func_array( $this->mFunctionHooks[$function], $funcArgs );
// The text is usually already parsed, doesn't need triple-brace tags expanded, etc.
//$noargs = true;
//$noparse = true;
-
+
if ( is_array( $result ) ) {
- $text = $linestart . $result[0];
- unset( $result[0] );
+ if ( isset( $result[0] ) ) {
+ $text = $linestart . $result[0];
+ unset( $result[0] );
+ }
// Extract flags into the local scope
// This allows callers to set flags such as nowiki, noparse, found, etc.
}
}
}
+ wfProfileOut( __METHOD__ . '-pfunc' );
}
# Template table test
# Load from database
$lastPathLevel = $this->mTemplatePath;
if ( !$found ) {
+ wfProfileIn( __METHOD__ . '-loadtpl' );
$ns = NS_TEMPLATE;
# declaring $subpage directly in the function call
# does not work correctly with references and breaks
}
$found = true;
}
-
+
# Template cache array insertion
# Use the original $piece['title'] not the mangled $part1, so that
# modifiers such as RAW: produce separate cache entries
$text = $linestart . $text;
}
}
+ wfProfileOut( __METHOD__ . '-loadtpl' );
}
# Recursive parsing, escaping and link table handling
wfProfileOut( $fname );
return $piece['text'];
} else {
+ wfProfileIn( __METHOD__ . '-placeholders' );
if ( $isHTML ) {
# Replace raw HTML by a placeholder
# Add a blank line preceding, to prevent it from mucking up
}
}
}
+ wfProfileOut( __METHOD__ . '-placeholders' );
}
# Prune lower levels off the recursion check path
function stripNoGallery( &$text ) {
# if the string __NOGALLERY__ (not case-sensitive) occurs in the HTML,
# do not add TOC
- $mw = MagicWord::get( MAG_NOGALLERY );
+ $mw = MagicWord::get( 'nogallery' );
$this->mOutput->mNoGallery = $mw->matchAndRemove( $text ) ;
}
function stripToc( $text ) {
# if the string __NOTOC__ (not case-sensitive) occurs in the HTML,
# do not add TOC
- $mw = MagicWord::get( MAG_NOTOC );
+ $mw = MagicWord::get( 'notoc' );
if( $mw->matchAndRemove( $text ) ) {
$this->mShowToc = false;
}
-
- $mw = MagicWord::get( MAG_TOC );
+
+ $mw = MagicWord::get( 'toc' );
if( $mw->match( $text ) ) {
$this->mShowToc = true;
$this->mForceTocPosition = true;
-
+
// Set a placeholder. At the end we'll fill it in with the TOC.
$text = $mw->replace( '<!--MWTOC-->', $text, 1 );
-
+
// Only keep the first one.
$text = $mw->replace( '', $text );
}
}
# Inhibit editsection links if requested in the page
- $esw =& MagicWord::get( MAG_NOEDITSECTION );
+ $esw =& MagicWord::get( 'noeditsection' );
if( $esw->matchAndRemove( $text ) ) {
$showEditLink = 0;
}
# Allow user to stipulate that a page should have a "new section"
# link added via __NEWSECTIONLINK__
- $mw =& MagicWord::get( MAG_NEWSECTIONLINK );
+ $mw =& MagicWord::get( 'newsectionlink' );
if( $mw->matchAndRemove( $text ) )
$this->mOutput->setNewSection( true );
# if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
# override above conditions and always show TOC above first header
- $mw =& MagicWord::get( MAG_FORCETOC );
+ $mw =& MagicWord::get( 'forcetoc' );
if ($mw->matchAndRemove( $text ) ) {
$this->mShowToc = true;
$enoughToc = true;
}
$isbn = $blank = '' ;
- while ( ' ' == $x{0} ) {
+ while ( $x !== '' && ' ' == $x{0} ) {
$blank .= ' ';
$x = substr( $x, 1 );
}
$text .= $keyword . $x;
continue;
}
-
+
$id = $blank = '' ;
/** remove and save whitespaces in $blank */
# Variable replacement
# Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
$text = $this->replaceVariables( $text );
-
+
# Strip out <nowiki> etc. added via replaceVariables
$text = $this->strip( $text, $stripState, false, array( 'gallery' ) );
-
+
# Signatures
$sigText = $this->getUserSig( $user );
$text = strtr( $text, array(
#
global $wgLegalTitleChars;
$tc = "[$wgLegalTitleChars]";
- $np = str_replace( array( '(', ')' ), array( '', '' ), $tc ); # No parens
$namespacechar = '[ _0-9A-Za-z\x80-\xff]'; # Namespaces can use non-ascii!
- $conpat = "/^({$np}+) \\(({$tc}+)\\)$/";
+ $conpat = "/^{$tc}+?( \\({$tc}+\\)|)$/";
- $p1 = "/\[\[({$np}+) \\(({$np}+)\\)\\|]]/"; # [[page (context)|]]
- $p2 = "/\[\[\\|({$tc}+)]]/"; # [[|page]]
- $p3 = "/\[\[(:*$namespacechar+):({$np}+)\\|]]/"; # [[namespace:page|]] and [[:namespace:page|]]
- $p4 = "/\[\[(:*$namespacechar+):({$np}+) \\(({$np}+)\\)\\|]]/"; # [[ns:page (cont)|]] and [[:ns:page (cont)|]]
- $context = '';
- $t = $this->mTitle->getText();
- if ( preg_match( $conpat, $t, $m ) ) {
- $context = $m[2];
- }
- $text = preg_replace( $p4, '[[\\1:\\2 (\\3)|\\2]]', $text );
- $text = preg_replace( $p1, '[[\\1 (\\2)|\\1]]', $text );
- $text = preg_replace( $p3, '[[\\1:\\2|\\2]]', $text );
+ $p1 = "/\[\[(:?$namespacechar+:|:|)({$tc}+?)( \\({$tc}+\\)|)\\|]]/"; # [[ns:page (context)|]]
+ $p2 = "/\[\[\\|({$tc}+)]]/"; # [[|page]]
- if ( '' == $context ) {
- $text = preg_replace( $p2, '[[\\1]]', $text );
+ $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
+
+ $t = $this->mTitle->getText();
+ if ( preg_match( $conpat, $t, $m ) && '' != $m[1] ) {
+ $text = preg_replace( $p2, "[[\\1{$m[1]}|\\1]]", $text );
} else {
- $text = preg_replace( $p2, "[[\\1 ({$context})|\\1]]", $text );
+ # if $m[1] is empty, don't bother duplicating the title
+ $text = preg_replace( $p2, '[[\\1]]', $text );
}
# Trim trailing whitespace
- # MAG_END (__END__) tag allows for trailing
+ # __END__ tag allows for trailing
# whitespace to be deliberately included
$text = rtrim( $text );
- $mw =& MagicWord::get( MAG_END );
+ $mw =& MagicWord::get( 'end' );
$mw->matchAndRemove( $text );
return $text;
$username = $user->getName();
$nickname = $user->getOption( 'nickname' );
$nickname = $nickname === '' ? $username : $nickname;
-
+
if( $user->getBoolOption( 'fancysig' ) !== false ) {
# Sig. might contain markup; validate this
if( $this->validateSig( $nickname ) !== false ) {
function validateSig( $text ) {
return( wfIsWellFormedXmlFragment( $text ) ? $text : false );
}
-
+
/**
* Clean up signature text
*
function cleanSig( $text, $parsing = false ) {
global $wgTitle;
$this->startExternalParse( $wgTitle, new ParserOptions(), $parsing ? OT_WIKI : OT_MSG );
-
- $substWord = MagicWord::get( MAG_SUBST );
+
+ $substWord = MagicWord::get( 'subst' );
$substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
$substText = '{{' . $substWord->getSynonym( 0 );
$text = preg_replace( $substRegex, $substText, $text );
$text = $this->cleanSigInSig( $text );
$text = $this->replaceVariables( $text );
-
- $this->clearState();
+
+ $this->clearState();
return $text;
}
$text = preg_replace( '/~{3,5}/', '', $text );
return $text;
}
-
+
/**
* Set up some variables which are usually set up in parse()
* so that an external function can call some class members with confidence
*
* @public
*
- * @param mixed $id The magic word ID, or (deprecated) the function name. Function names are case-insensitive.
+ * @param string $id The magic word ID
* @param mixed $callback The callback function (and object) to use
+ * @param integer $flags a combination of the following flags:
+ * SFH_NO_HASH No leading hash, i.e. {{plural:...}} instead of {{#if:...}}
*
* @return The old callback function for this name, if any
*/
- function setFunctionHook( $id, $callback ) {
- if( is_string( $id ) ) {
- $id = strtolower( $id );
- }
+ function setFunctionHook( $id, $callback, $flags = 0 ) {
$oldVal = @$this->mFunctionHooks[$id];
$this->mFunctionHooks[$id] = $callback;
+
+ # Add to function cache
+ $mw = MagicWord::get( $id );
+ if ( !$mw ) {
+ throw new MWException( 'The calling convention to Parser::setFunctionHook() has changed, ' .
+ 'it is now required to pass a MagicWord ID as the first parameter.' );
+ }
+
+ $synonyms = $mw->getSynonyms();
+ $sensitive = intval( $mw->isCaseSensitive() );
+
+ foreach ( $synonyms as $syn ) {
+ # Case
+ if ( !$sensitive ) {
+ $syn = strtolower( $syn );
+ }
+ # Add leading hash
+ if ( !( $flags & SFH_NO_HASH ) ) {
+ $syn = '#' . $syn;
+ }
+ # Remove trailing colon
+ if ( substr( $syn, -1, 1 ) == ':' ) {
+ $syn = substr( $syn, 0, -1 );
+ }
+ $this->mFunctionSynonyms[$sensitive][$syn] = $id;
+ }
return $oldVal;
}
function renderPreTag( $text, $attribs, $parser ) {
// Backwards-compatibility hack
$content = preg_replace( '!<nowiki>(.*?)</nowiki>!is', '\\1', $text );
-
+
$attribs = Sanitizer::validateTagAttributes( $attribs, 'pre' );
return wfOpenElement( 'pre', $attribs ) .
wfEscapeHTMLTagsOnly( $content ) .
'</pre>';
}
-
+
/**
* Renders an image gallery from a text with one line per image.
* text labels may be given by using |-style alternative text. E.g.
if( isset( $params['caption'] ) )
$ig->setCaption( $params['caption'] );
-
+
$lines = explode( "\n", $text );
foreach ( $lines as $line ) {
# match lines like these:
if ( count( $matches ) == 0 ) {
continue;
}
- $nt =& Title::newFromText( $matches[1] );
+ $tp = Title::newFromText( $matches[1] );
+ $nt =& $tp;
if( is_null( $nt ) ) {
# Bogus title. Ignore these so we don't bomb out later.
continue;
$part = explode( '|', $options);
- $mwThumb =& MagicWord::get( MAG_IMG_THUMBNAIL );
- $mwManualThumb =& MagicWord::get( MAG_IMG_MANUALTHUMB );
- $mwLeft =& MagicWord::get( MAG_IMG_LEFT );
- $mwRight =& MagicWord::get( MAG_IMG_RIGHT );
- $mwNone =& MagicWord::get( MAG_IMG_NONE );
- $mwWidth =& MagicWord::get( MAG_IMG_WIDTH );
- $mwCenter =& MagicWord::get( MAG_IMG_CENTER );
- $mwFramed =& MagicWord::get( MAG_IMG_FRAMED );
+ $mwThumb =& MagicWord::get( 'img_thumbnail' );
+ $mwManualThumb =& MagicWord::get( 'img_manualthumb' );
+ $mwLeft =& MagicWord::get( 'img_left' );
+ $mwRight =& MagicWord::get( 'img_right' );
+ $mwNone =& MagicWord::get( 'img_none' );
+ $mwWidth =& MagicWord::get( 'img_width' );
+ $mwCenter =& MagicWord::get( 'img_center' );
+ $mwFramed =& MagicWord::get( 'img_framed' );
$caption = '';
$width = $height = $framed = $thumb = false;
# remember to set an alignment, don't render immediately
$align = 'none';
} elseif ( $wgUseImageResize && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
- wfDebug( "MAG_IMG_WIDTH match: $match\n" );
+ wfDebug( "img_width match: $match\n" );
# $match is the image width in pixels
if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) {
$width = intval( $m[1] );
# strip NOWIKI etc. to avoid confusion (true-parameter causes HTML
# comments to be stripped as well)
$striparray = array();
-
+
$oldOutputType = $this->mOutputType;
$oldOptions = $this->mOptions;
$this->mOptions = new ParserOptions();
$this->mOutputType = OT_WIKI;
-
+
$striptext = $this->strip( $text, $striparray, true );
-
+
$this->mOutputType = $oldOutputType;
$this->mOptions = $oldOptions;
/mix",
$striptext, -1,
PREG_SPLIT_DELIM_CAPTURE);
-
+
if( $mode == "get" ) {
if( $section == 0 ) {
// "Section 0" returns the content before any other section.
$rv = trim( $rv );
return $rv;
}
-
+
/**
* This function returns the text of a section, specified by a number ($section).
* A section is text under a heading like == Heading == or \<h1\>Heading\</h1\>, or
function getSection( $text, $section ) {
return $this->extractSections( $text, $section, "get" );
}
-
+
function replaceSection( $oldtext, $section, $text ) {
return $this->extractSections( $oldtext, $section, "replace", $text );
}
function &getImages() { return $this->mImages; }
function &getExternalLinks() { return $this->mExternalLinks; }
function getNoGallery() { return $this->mNoGallery; }
+ function getSubtitle() { return $this->mSubtitle; }
function containsOldMagic() { return $this->mContainsOldMagic; }
function setText( $text ) { return wfSetVar( $this->mText, $text ); }
function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategories, $cl ); }
function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); }
function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); }
- function setTitleText( $t ) { return wfSetVar ($this->mTitleText, $t); }
+ function setTitleText( $t ) { return wfSetVar($this->mTitleText, $t); }
+ function setSubtitle( $st ) { return wfSetVar( $this->mSubtitle, $st ); }
function addCategory( $c, $sort ) { $this->mCategories[$c] = $sort; }
function addImage( $name ) { $this->mImages[$name] = 1; }
function getInterwikiMagic() { return $this->mInterwikiMagic; }
function getAllowExternalImages() { return $this->mAllowExternalImages; }
function getAllowExternalImagesFrom() { return $this->mAllowExternalImagesFrom; }
- function getDateFormat() { return $this->mDateFormat; }
function getEditSection() { return $this->mEditSection; }
function getNumberHeadings() { return $this->mNumberHeadings; }
function getAllowSpecialInclusion() { return $this->mAllowSpecialInclusion; }
return $this->mSkin;
}
+ function getDateFormat() {
+ if ( !isset( $this->mDateFormat ) ) {
+ $this->mDateFormat = $this->mUser->getDatePreference();
+ }
+ return $this->mDateFormat;
+ }
+
function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); }
function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); }
function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); }
* Get parser options
* @static
*/
- function newFromUser( &$user ) {
+ static function newFromUser( $user ) {
return new ParserOptions( $user );
}
/** Get user options */
- function initialiseFromUser( &$userInput ) {
+ function initialiseFromUser( $userInput ) {
global $wgUseTeX, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages;
global $wgAllowExternalImagesFrom, $wgAllowSpecialInclusion;
$fname = 'ParserOptions::initialiseFromUser';
$this->mAllowExternalImages = $wgAllowExternalImages;
$this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom;
$this->mSkin = null; # Deferred
- $this->mDateFormat = $user->getOption( 'date' );
+ $this->mDateFormat = null; # Deferred
$this->mEditSection = true;
$this->mNumberHeadings = $user->getOption( 'numberheadings' );
$this->mAllowSpecialInclusion = $wgAllowSpecialInclusion;