closeParagraph();
if($preOpenMatch and !$preCloseMatch) {
$this->mInPre = true;
}
if ( $closematch ) {
$inBlockElem = false;
} else {
$inBlockElem = true;
}
} else if ( !$inBlockElem && !$this->mInPre ) {
if ( ' ' == $t{0} and ( $this->mLastSection == 'pre' or trim($t) != '' ) ) {
// pre
if ($this->mLastSection != 'pre') {
$paragraphStack = false;
$output .= $this->closeParagraph().'';
$this->mLastSection = 'pre';
}
$t = substr( $t, 1 );
} else {
// paragraph
if ( '' == trim($t) ) {
if ( $paragraphStack ) {
$output .= $paragraphStack.'
';
$paragraphStack = false;
$this->mLastSection = 'p';
} else {
if ($this->mLastSection != 'p' ) {
$output .= $this->closeParagraph();
$this->mLastSection = '';
$paragraphStack = '';
} else {
$paragraphStack = '
';
}
}
} else {
if ( $paragraphStack ) {
$output .= $paragraphStack;
$paragraphStack = false;
$this->mLastSection = 'p';
} else if ($this->mLastSection != 'p') {
$output .= $this->closeParagraph().'
';
$this->mLastSection = 'p';
}
}
}
}
}
if ($paragraphStack === false) {
$output .= $t."\n";
}
}
while ( $prefixLength ) {
$output .= $this->closeList( $pref2{$prefixLength-1} );
--$prefixLength;
}
if ( '' != $this->mLastSection ) {
$output .= '' . $this->mLastSection . '>';
$this->mLastSection = '';
}
wfProfileOut( $fname );
return $output;
}
/**
* Return value of a magic variable (like PAGENAME)
*
* @access private
*/
function getVariableValue( $index ) {
global $wgLang, $wgSitename, $wgServer;
switch ( $index ) {
case MAG_CURRENTMONTH:
return $wgLang->formatNum( date( 'm' ) );
case MAG_CURRENTMONTHNAME:
return $wgLang->getMonthName( date('n') );
case MAG_CURRENTMONTHNAMEGEN:
return $wgLang->getMonthNameGen( date('n') );
case MAG_CURRENTDAY:
return $wgLang->formatNum( date('j') );
case MAG_PAGENAME:
return $this->mTitle->getText();
case MAG_PAGENAMEE:
return $this->mTitle->getPartialURL();
case MAG_NAMESPACE:
# return Namespace::getCanonicalName($this->mTitle->getNamespace());
return $wgLang->getNsText($this->mTitle->getNamespace()); # Patch by Dori
case MAG_CURRENTDAYNAME:
return $wgLang->getWeekdayName( date('w')+1 );
case MAG_CURRENTYEAR:
return $wgLang->formatNum( date( 'Y' ) );
case MAG_CURRENTTIME:
return $wgLang->time( wfTimestampNow(), false );
case MAG_NUMBEROFARTICLES:
return $wgLang->formatNum( wfNumberOfArticles() );
case MAG_SITENAME:
return $wgSitename;
case MAG_SERVER:
return $wgServer;
default:
return NULL;
}
}
/**
* initialise the magic variables (like CURRENTMONTHNAME)
*
* @access private
*/
function initialiseVariables() {
global $wgVariableIDs;
$this->mVariables = array();
foreach ( $wgVariableIDs as $id ) {
$mw =& MagicWord::get( $id );
$mw->addToArray( $this->mVariables, $this->getVariableValue( $id ) );
}
}
/**
* Replace magic variables, templates, and template arguments
* with the appropriate text. Templates are substituted recursively,
* taking care to avoid infinite loops.
*
* Note that the substitution depends on value of $mOutputType:
* OT_WIKI: only {{subst:}} templates
* OT_MSG: only magic variables
* OT_HTML: all templates and magic variables
*
* @param string $tex The text to transform
* @param array $args Key-value pairs representing template parameters to substitute
* @access private
*/
function replaceVariables( $text, $args = array() ) {
global $wgLang, $wgScript, $wgArticlePath;
# Prevent too big inclusions
if(strlen($text)> MAX_INCLUDE_SIZE)
return $text;
$fname = 'Parser::replaceVariables';
wfProfileIn( $fname );
$titleChars = Title::legalChars();
# This function is called recursively. To keep track of arguments we need a stack:
array_push( $this->mArgStack, $args );
# PHP global rebinding syntax is a bit weird, need to use the GLOBALS array
$GLOBALS['wgCurParser'] =& $this;
# Variable substitution
$text = preg_replace_callback( "/{{([$titleChars]*?)}}/", 'wfVariableSubstitution', $text );
if ( $this->mOutputType == OT_HTML || $this->mOutputType == OT_WIKI ) {
# Argument substitution
$text = preg_replace_callback( "/{{{([$titleChars]*?)}}}/", 'wfArgSubstitution', $text );
}
# Template substitution
$regex = '/{{(['.$titleChars.']*)(\\|.*?|)}}/s';
$text = preg_replace_callback( $regex, 'wfBraceSubstitution', $text );
array_pop( $this->mArgStack );
wfProfileOut( $fname );
return $text;
}
/**
* Replace magic variables
* @access private
*/
function variableSubstitution( $matches ) {
if ( !$this->mVariables ) {
$this->initialiseVariables();
}
$skip = false;
if ( $this->mOutputType == OT_WIKI ) {
# Do only magic variables prefixed by SUBST
$mwSubst =& MagicWord::get( MAG_SUBST );
if (!$mwSubst->matchStartAndRemove( $matches[1] ))
$skip = true;
# Note that if we don't substitute the variable below,
# we don't remove the {{subst:}} magic word, in case
# it is a template rather than a magic variable.
}
if ( !$skip && array_key_exists( $matches[1], $this->mVariables ) ) {
$text = $this->mVariables[$matches[1]];
$this->mOutput->mContainsOldMagic = true;
} else {
$text = $matches[0];
}
return $text;
}
# Split template arguments
function getTemplateArgs( $argsString ) {
if ( $argsString === '' ) {
return array();
}
$args = explode( '|', substr( $argsString, 1 ) );
# If any of the arguments contains a '[[' but no ']]', it needs to be
# merged with the next arg because the '|' character between belongs
# to the link syntax and not the template parameter syntax.
$argc = count($args);
$i = 0;
for ( $i = 0; $i < $argc-1; $i++ ) {
if ( substr_count ( $args[$i], '[[' ) != substr_count ( $args[$i], ']]' ) ) {
$args[$i] .= '|'.$args[$i+1];
array_splice($args, $i+1, 1);
$i--;
$argc--;
}
}
return $args;
}
/**
* Return the text of a template, after recursively
* replacing any variables or templates within the template.
*
* @param array $matches The parts of the template
* $matches[1]: the title, i.e. the part before the |
* $matches[2]: the parameters (including a leading |), if any
* @return string the text of the template
* @access private
*/
function braceSubstitution( $matches ) {
global $wgLinkCache, $wgLang;
$fname = 'Parser::braceSubstitution';
$found = false;
$nowiki = false;
$noparse = false;
$itcamefromthedatabase = false;
$title = NULL;
# $part1 is the bit before the first |, and must contain only title characters
# $args is a list of arguments, starting from index 0, not including $part1
$part1 = $matches[1];
# If the second subpattern matched anything, it will start with |
$args = $this->getTemplateArgs($matches[2]);
$argc = count( $args );
# {{{}}}
if ( strpos( $matches[0], '{{{' ) !== false ) {
$text = $matches[0];
$found = true;
$noparse = true;
}
# SUBST
if ( !$found ) {
$mwSubst =& MagicWord::get( MAG_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
# 2) Didn't find SUBST and in the PST phase
# In either case, return without further processing
$text = $matches[0];
$found = true;
$noparse = true;
}
}
# MSG, MSGNW and INT
if ( !$found ) {
# Check for MSGNW:
$mwMsgnw =& MagicWord::get( MAG_MSGNW );
if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
$nowiki = true;
} else {
# Remove obsolete MSG:
$mwMsg =& MagicWord::get( MAG_MSG );
$mwMsg->matchStartAndRemove( $part1 );
}
# Check if it is an internal message
$mwInt =& MagicWord::get( MAG_INT );
if ( $mwInt->matchStartAndRemove( $part1 ) ) {
if ( $this->incrementIncludeCount( 'int:'.$part1 ) ) {
$text = wfMsgReal( $part1, $args, true );
$found = true;
}
}
}
# NS
if ( !$found ) {
# Check for NS: (namespace expansion)
$mwNs = MagicWord::get( MAG_NS );
if ( $mwNs->matchStartAndRemove( $part1 ) ) {
if ( intval( $part1 ) ) {
$text = $wgLang->getNsText( intval( $part1 ) );
$found = true;
} else {
$index = Namespace::getCanonicalIndex( strtolower( $part1 ) );
if ( !is_null( $index ) ) {
$text = $wgLang->getNsText( $index );
$found = true;
}
}
}
}
# LOCALURL and LOCALURLE
if ( !$found ) {
$mwLocal = MagicWord::get( MAG_LOCALURL );
$mwLocalE = MagicWord::get( MAG_LOCALURLE );
if ( $mwLocal->matchStartAndRemove( $part1 ) ) {
$func = 'getLocalURL';
} elseif ( $mwLocalE->matchStartAndRemove( $part1 ) ) {
$func = 'escapeLocalURL';
} else {
$func = '';
}
if ( $func !== '' ) {
$title = Title::newFromText( $part1 );
if ( !is_null( $title ) ) {
if ( $argc > 0 ) {
$text = $title->$func( $args[0] );
} else {
$text = $title->$func();
}
$found = true;
}
}
}
# GRAMMAR
if ( !$found && $argc == 1 ) {
$mwGrammar =& MagicWord::get( MAG_GRAMMAR );
if ( $mwGrammar->matchStartAndRemove( $part1 ) ) {
$text = $wgLang->convertGrammar( $args[0], $part1 );
$found = true;
}
}
# Template table test
# Did we encounter this template already? If yes, it is in the cache
# and we need to check for loops.
if ( isset( $this->mTemplates[$part1] ) ) {
# Infinite loop test
if ( isset( $this->mTemplatePath[$part1] ) ) {
$noparse = true;
$found = true;
}
# set $text to cached message.
$text = $this->mTemplates[$part1];
$found = true;
}
# Load from database
if ( !$found ) {
$title = Title::newFromText( $part1, NS_TEMPLATE );
if ( !is_null( $title ) && !$title->isExternal() ) {
# Check for excessive inclusion
$dbk = $title->getPrefixedDBkey();
if ( $this->incrementIncludeCount( $dbk ) ) {
# This should never be reached.
$article = new Article( $title );
$articleContent = $article->getContentWithoutUsingSoManyDamnGlobals();
if ( $articleContent !== false ) {
$found = true;
$text = $articleContent;
$itcamefromthedatabase = true;
}
}
# If the title is valid but undisplayable, make a link to it
if ( $this->mOutputType == OT_HTML && !$found ) {
$text = '[['.$title->getPrefixedText().']]';
$found = true;
}
# Template cache array insertion
$this->mTemplates[$part1] = $text;
}
}
# Recursive parsing, escaping and link table handling
# Only for HTML output
if ( $nowiki && $found && $this->mOutputType == OT_HTML ) {
$text = wfEscapeWikiText( $text );
} elseif ( ($this->mOutputType == OT_HTML || $this->mOutputType == OT_WIKI) && $found && !$noparse) {
# Clean up argument array
$assocArgs = array();
$index = 1;
foreach( $args as $arg ) {
$eqpos = strpos( $arg, '=' );
if ( $eqpos === false ) {
$assocArgs[$index++] = $arg;
} else {
$name = trim( substr( $arg, 0, $eqpos ) );
$value = trim( substr( $arg, $eqpos+1 ) );
if ( $value === false ) {
$value = '';
}
if ( $name !== false ) {
$assocArgs[$name] = $value;
}
}
}
# Add a new element to the templace recursion path
$this->mTemplatePath[$part1] = 1;
$text = $this->strip( $text, $this->mStripState );
$text = $this->removeHTMLtags( $text );
$text = $this->replaceVariables( $text, $assocArgs );
# Resume the link cache and register the inclusion as a link
if ( $mOutputType == OT_HTML && !is_null( $title ) ) {
$wgLinkCache->addLinkObj( $title );
}
}
# Empties the template path
$this->mTemplatePath = array();
if ( !$found ) {
return $matches[0];
} else {
# replace ==section headers==
# XXX this needs to go away once we have a better parser.
if ( $this->mOutputType != OT_WIKI && $itcamefromthedatabase ) {
if( !is_null( $title ) )
$encodedname = base64_encode($title->getPrefixedDBkey());
else
$encodedname = base64_encode("");
$matches = preg_split('/(^={1,6}.*?={1,6}\s*?$)/m', $text, -1,
PREG_SPLIT_DELIM_CAPTURE);
$text = '';
$nsec = 0;
for( $i = 0; $i < count($matches); $i += 2 ) {
$text .= $matches[$i];
if (!isset($matches[$i + 1]) || $matches[$i + 1] == "") continue;
$hl = $matches[$i + 1];
if( strstr($hl, "" . $m2[3];
$nsec++;
}
}
return $text;
}
}
/**
* Triple brace replacement -- used for template arguments
* @access private
*/
function argSubstitution( $matches ) {
$arg = trim( $matches[1] );
$text = $matches[0];
$inputArgs = end( $this->mArgStack );
if ( array_key_exists( $arg, $inputArgs ) ) {
$text = $inputArgs[$arg];
}
return $text;
}
/**
* Returns true if the function is allowed to include this entity
* @access private
*/
function incrementIncludeCount( $dbk ) {
if ( !array_key_exists( $dbk, $this->mIncludeCount ) ) {
$this->mIncludeCount[$dbk] = 0;
}
if ( ++$this->mIncludeCount[$dbk] <= MAX_INCLUDE_REPEAT ) {
return true;
} else {
return false;
}
}
/**
* Cleans up HTML, removes dangerous tags and attributes, and
* removes HTML comments
* @access private
*/
function removeHTMLtags( $text ) {
global $wgUseTidy, $wgUserHtml;
$fname = 'Parser::removeHTMLtags';
wfProfileIn( $fname );
if( $wgUserHtml ) {
$htmlpairs = array( # Tags that must be closed
'b', 'del', 'i', 'ins', 'u', 'font', 'big', 'small', 'sub', 'sup', 'h1',
'h2', 'h3', 'h4', 'h5', 'h6', 'cite', 'code', 'em', 's',
'strike', 'strong', 'tt', 'var', 'div', 'center',
'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre',
'ruby', 'rt' , 'rb' , 'rp', 'p'
);
$htmlsingle = array(
'br', 'hr', 'li', 'dt', 'dd'
);
$htmlnest = array( # Tags that can be nested--??
'table', 'tr', 'td', 'th', 'div', 'blockquote', 'ol', 'ul',
'dl', 'font', 'big', 'small', 'sub', 'sup'
);
$tabletags = array( # Can only appear inside table
'td', 'th', 'tr'
);
} else {
$htmlpairs = array();
$htmlsingle = array();
$htmlnest = array();
$tabletags = array();
}
$htmlsingle = array_merge( $tabletags, $htmlsingle );
$htmlelements = array_merge( $htmlsingle, $htmlpairs );
$htmlattrs = $this->getHTMLattrs () ;
# Remove HTML comments
$text = $this->removeHTMLcomments( $text );
$bits = explode( '<', $text );
$text = array_shift( $bits );
if(!$wgUseTidy) {
$tagstack = array(); $tablestack = array();
foreach ( $bits as $x ) {
$prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) );
preg_match( '/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/',
$x, $regs );
list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
error_reporting( $prev );
$badtag = 0 ;
if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
# Check our stack
if ( $slash ) {
# Closing a tag...
if ( ! in_array( $t, $htmlsingle ) &&
( $ot = @array_pop( $tagstack ) ) != $t ) {
@array_push( $tagstack, $ot );
$badtag = 1;
} else {
if ( $t == 'table' ) {
$tagstack = array_pop( $tablestack );
}
$newparams = '';
}
} else {
# Keep track for later
if ( in_array( $t, $tabletags ) &&
! in_array( 'table', $tagstack ) ) {
$badtag = 1;
} else if ( in_array( $t, $tagstack ) &&
! in_array ( $t , $htmlnest ) ) {
$badtag = 1 ;
} else if ( ! in_array( $t, $htmlsingle ) ) {
if ( $t == 'table' ) {
array_push( $tablestack, $tagstack );
$tagstack = array();
}
array_push( $tagstack, $t );
}
# Strip non-approved attributes from the tag
$newparams = $this->fixTagAttributes($params);
}
if ( ! $badtag ) {
$rest = str_replace( '>', '>', $rest );
$text .= "<$slash$t $newparams$brace$rest";
continue;
}
}
$text .= '<' . str_replace( '>', '>', $x);
}
# Close off any remaining tags
while ( is_array( $tagstack ) && ($t = array_pop( $tagstack )) ) {
$text .= "$t>\n";
if ( $t == 'table' ) { $tagstack = array_pop( $tablestack ); }
}
} else {
# this might be possible using tidy itself
foreach ( $bits as $x ) {
preg_match( '/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/',
$x, $regs );
@list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
$newparams = $this->fixTagAttributes($params);
$rest = str_replace( '>', '>', $rest );
$text .= "<$slash$t $newparams$brace$rest";
} else {
$text .= '<' . str_replace( '>', '>', $x);
}
}
}
wfProfileOut( $fname );
return $text;
}
/**
* Remove '', and everything between.
* To avoid leaving blank lines, when a comment is both preceded
* and followed by a newline (ignoring spaces), trim leading and
* trailing spaces and one of the newlines.
*
* @access private
*/
function removeHTMLcomments( $text ) {
$fname='Parser::removeHTMLcomments';
wfProfileIn( $fname );
while (($start = strpos($text, '', $start + 4);
if ($end === false) {
# Unterminated comment; bail out
break;
}
$end += 3;
# Trim space and newline if the comment is both
# preceded and followed by a newline
$spaceStart = max($start - 1, 0);
$spaceLen = $end - $spaceStart;
while (substr($text, $spaceStart, 1) === ' ' && $spaceStart > 0) {
$spaceStart--;
$spaceLen++;
}
while (substr($text, $spaceStart + $spaceLen, 1) === ' ')
$spaceLen++;
if (substr($text, $spaceStart, 1) === "\n" and substr($text, $spaceStart + $spaceLen, 1) === "\n") {
# Remove the comment, leading and trailing
# spaces, and leave only one newline.
$text = substr_replace($text, "\n", $spaceStart, $spaceLen + 1);
}
else {
# Remove just the comment.
$text = substr_replace($text, '', $start, $end - $start);
}
}
wfProfileOut( $fname );
return $text;
}
/**
* This function accomplishes several tasks:
* 1) Auto-number headings if that option is enabled
* 2) Add an [edit] link to sections for logged in users who have enabled the option
* 3) Add a Table of contents on the top for users who have enabled the option
* 4) Auto-anchor headings
*
* It loops through all headlines, collects the necessary data, then splits up the
* string and re-inserts the newly formatted headlines.
* @access private
*/
/* private */ function formatHeadings( $text, $isMain=true ) {
global $wgInputEncoding, $wgMaxTocLevel, $wgLang, $wgLinkHolders;
$doNumberHeadings = $this->mOptions->getNumberHeadings();
$doShowToc = $this->mOptions->getShowToc();
$forceTocHere = false;
if( !$this->mTitle->userCanEdit() ) {
$showEditLink = 0;
$rightClickHack = 0;
} else {
$showEditLink = $this->mOptions->getEditSection();
$rightClickHack = $this->mOptions->getEditSectionOnRightClick();
}
# Inhibit editsection links if requested in the page
$esw =& MagicWord::get( MAG_NOEDITSECTION );
if( $esw->matchAndRemove( $text ) ) {
$showEditLink = 0;
}
# if the string __NOTOC__ (not case-sensitive) occurs in the HTML,
# do not add TOC
$mw =& MagicWord::get( MAG_NOTOC );
if( $mw->matchAndRemove( $text ) ) {
$doShowToc = 0;
}
# never add the TOC to the Main Page. This is an entry page that should not
# be more than 1-2 screens large anyway
if( $this->mTitle->getPrefixedText() == wfMsg('mainpage') ) {
$doShowToc = 0;
}
# Get all headlines for numbering them and adding funky stuff like [edit]
# links - this is for later, but we need the number of headlines right now
$numMatches = preg_match_all( '/)(.*?)<\/H[1-6]>/i', $text, $matches );
# if there are fewer than 4 headlines in the article, do not show TOC
if( $numMatches < 4 ) {
$doShowToc = 0;
}
# if the string __TOC__ (not case-sensitive) occurs in the HTML,
# override above conditions and always show TOC at that place
$mw =& MagicWord::get( MAG_TOC );
if ($mw->match( $text ) ) {
$doShowToc = 1;
$forceTocHere = true;
} else {
# 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 );
if ($mw->matchAndRemove( $text ) ) {
$doShowToc = 1;
}
}
# We need this to perform operations on the HTML
$sk =& $this->mOptions->getSkin();
# headline counter
$headlineCount = 0;
$sectionCount = 0; # headlineCount excluding template sections
# Ugh .. the TOC should have neat indentation levels which can be
# passed to the skin functions. These are determined here
$toclevel = 0;
$toc = '';
$full = '';
$head = array();
$sublevelCount = array();
$level = 0;
$prevlevel = 0;
foreach( $matches[3] as $headline ) {
$istemplate = 0;
$templatetitle = "";
$templatesection = 0;
if (preg_match("//", $headline, $mat)) {
$istemplate = 1;
$templatetitle = base64_decode($mat[1]);
$templatesection = 1 + (int)base64_decode($mat[2]);
$headline = preg_replace("//", "", $headline);
}
$numbering = '';
if( $level ) {
$prevlevel = $level;
}
$level = $matches[1][$headlineCount];
if( ( $doNumberHeadings || $doShowToc ) && $prevlevel && $level > $prevlevel ) {
# reset when we enter a new level
$sublevelCount[$level] = 0;
$toc .= $sk->tocIndent( $level - $prevlevel );
$toclevel += $level - $prevlevel;
}
if( ( $doNumberHeadings || $doShowToc ) && $level < $prevlevel ) {
# reset when we step back a level
$sublevelCount[$level+1]=0;
$toc .= $sk->tocUnindent( $prevlevel - $level );
$toclevel -= $prevlevel - $level;
}
# count number of headlines for each level
@$sublevelCount[$level]++;
if( $doNumberHeadings || $doShowToc ) {
$dot = 0;
for( $i = 1; $i <= $level; $i++ ) {
if( !empty( $sublevelCount[$i] ) ) {
if( $dot ) {
$numbering .= '.';
}
$numbering .= $wgLang->formatNum( $sublevelCount[$i] );
$dot = 1;
}
}
}
# The canonized header is a version of the header text safe to use for links
# Avoid insertion of weird stuff like