/**
- * PHP Parser - Processes wiki markup (which uses a more user-friendly
+ * PHP Parser - Processes wiki markup (which uses a more user-friendly
* syntax, such as "[[link]]" for making links), and provides a one-way
* transformation of that wiki markup it into XHTML output / markup
* (which in turn the browser understands, and can display).
const OT_WIKI = 2;
const OT_PREPROCESS = 3;
const OT_MSG = 3;
-
+
+ // Marker Suffix needs to be accessible staticly.
+ const MARKER_SUFFIX = "-QINU\x7f";
+
/**#@+
* @private
*/
# Persistent:
var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables,
- $mImageParams, $mImageParamsMagicArray, $mStripList, $mMarkerSuffix,
- $mExtLinkBracketedRegex, $mPreprocessor, $mDefaultStripList, $mVarCache, $mConf;
+ $mImageParams, $mImageParamsMagicArray, $mStripList, $mMarkerIndex, $mPreprocessor,
+ $mExtLinkBracketedRegex, $mDefaultStripList, $mVarCache, $mConf;
# Cleared with clearState():
var $mInterwikiLinkHolders, $mLinkHolders;
var $mIncludeSizes, $mPPNodeCount, $mDefaultSort;
var $mTplExpandCache; // empty-frame expansion cache
- var $mTplRedirCache, $mTplDomCache, $mHeadings;
+ var $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores;
+ var $mExpensiveFunctionCount; // number of expensive parser function calls
# Temporary
# These are variables reset at least once per parse regardless of $clearState
$this->mFunctionHooks = array();
$this->mFunctionSynonyms = array( 0 => array(), 1 => array() );
$this->mDefaultStripList = $this->mStripList = array( 'nowiki', 'gallery' );
- $this->mMarkerSuffix = "-QINU\x7f";
$this->mExtLinkBracketedRegex = '/\[(\b(' . wfUrlProtocols() . ')'.
'[^][<>"\\x00-\\x20\\x7F]+) *([^\]\\x0a\\x0d]*?)\]/S';
$this->mVarCache = array();
} else {
$this->mPreprocessorClass = 'Preprocessor_DOM';
}
+ $this->mMarkerIndex = 0;
$this->mFirstCall = true;
}
-
+
/**
* Do various kinds of initialisation on the first call of the parser
*/
return;
}
$this->mFirstCall = false;
-
+
wfProfileIn( __METHOD__ );
- global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions;
$this->setHook( 'pre', array( $this, 'renderPreTag' ) );
-
- # Syntax for arguments (see self::setFunctionHook):
- # "name for lookup in localized magic words array",
- # function callback,
- # optional SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}
- # instead of {{#int:...}})
- $this->setFunctionHook( 'int', array( 'CoreParserFunctions', 'intFunction' ), SFH_NO_HASH );
- $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( 'numberofedits', array( 'CoreParserFunctions', 'numberofedits' ), SFH_NO_HASH );
- $this->setFunctionHook( 'language', array( 'CoreParserFunctions', 'language' ), SFH_NO_HASH );
- $this->setFunctionHook( 'padleft', array( 'CoreParserFunctions', 'padleft' ), SFH_NO_HASH );
- $this->setFunctionHook( 'padright', array( 'CoreParserFunctions', 'padright' ), SFH_NO_HASH );
- $this->setFunctionHook( 'anchorencode', array( 'CoreParserFunctions', 'anchorencode' ), SFH_NO_HASH );
- $this->setFunctionHook( 'special', array( 'CoreParserFunctions', 'special' ) );
- $this->setFunctionHook( 'defaultsort', array( 'CoreParserFunctions', 'defaultsort' ), SFH_NO_HASH );
- $this->setFunctionHook( 'filepath', array( 'CoreParserFunctions', 'filepath' ), SFH_NO_HASH );
- $this->setFunctionHook( 'tag', array( 'CoreParserFunctions', 'tagObj' ), SFH_OBJECT_ARGS );
-
- if ( $wgAllowDisplayTitle ) {
- $this->setFunctionHook( 'displaytitle', array( 'CoreParserFunctions', 'displaytitle' ), SFH_NO_HASH );
- }
- if ( $wgAllowSlowParserFunctions ) {
- $this->setFunctionHook( 'pagesinnamespace', array( 'CoreParserFunctions', 'pagesinnamespace' ), SFH_NO_HASH );
- }
-
+ CoreParserFunctions::register( $this );
$this->initialiseVariables();
wfRunHooks( 'ParserFirstCallInit', array( &$this ) );
* since it shouldn't match when butted up against identifier-like
* string constructs.
*
- * Must not consist of all title characters, or else it will change
+ * Must not consist of all title characters, or else it will change
* the behaviour of <nowiki> in a link.
*/
#$this->mUniqPrefix = "\x07UNIQ" . Parser::getRandomString();
# Changed to \x7f to allow XML double-parsing -- TS
$this->mUniqPrefix = "\x7fUNIQ" . Parser::getRandomString();
+
# Clear these on every parse, bug 4549
$this->mTplExpandCache = $this->mTplRedirCache = $this->mTplDomCache = array();
$this->mPPNodeCount = 0;
$this->mDefaultSort = false;
$this->mHeadings = array();
+ $this->mDoubleUnderscores = array();
+ $this->mExpensiveFunctionCount = 0;
# Fix cloning
if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
'/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1 \\2',
# french spaces, Guillemet-right
'/(\\302\\253) /' => '\\1 ',
+ '/ (!\s*important)/' => ' \\1', #Beware of CSS magic word !important, bug #11874.
);
$text = preg_replace( array_keys($fixtags), array_values($fixtags), $text );
array_values( $tidyregs ),
$text );
}
+ global $wgExpensiveParserFunctionLimit;
+ if ( $this->mExpensiveFunctionCount > $wgExpensiveParserFunctionLimit ) {
+ if ( is_callable( array( $this->mOutput, 'addWarning' ) ) ) {
+ $warning = wfMsg( 'expensive-parserfunction-warning', $this->mExpensiveFunctionCount, $wgExpensiveParserFunctionLimit );
+ $this->mOutput->addWarning( $warning );
+ $cat = Title::makeTitleSafe( NS_CATEGORY, wfMsgForContent( 'expensive-parserfunction-category' ) );
+ if ( $cat ) {
+ $this->mOutput->addCategory( $cat->getDBkey(), $this->getDefaultSort() );
+ }
+ }
+ }
wfRunHooks( 'ParserAfterTidy', array( &$this, &$text ) );
# Information on include size limits, for the benefit of users who try to skirt them
if ( $this->mOptions->getEnableLimitReport() ) {
+ global $wgExpensiveParserFunctionLimit;
$max = $this->mOptions->getMaxIncludeSize();
- $limitReport =
- "NewPP limit report\n" .
+ $PFreport = "Expensive parser function count: {$this->mExpensiveFunctionCount}/$wgExpensiveParserFunctionLimit\n";
+ $limitReport =
+ "NewPP limit report\n" .
"Preprocessor node count: {$this->mPPNodeCount}/{$this->mOptions->mMaxPPNodeCount}\n" .
"Post-expand include size: {$this->mIncludeSizes['post-expand']}/$max bytes\n" .
- "Template argument size: {$this->mIncludeSizes['arg']}/$max bytes\n";
+ "Template argument size: {$this->mIncludeSizes['arg']}/$max bytes\n".
+ $PFreport;
wfRunHooks( 'ParserLimitReport', array( $this, &$limitReport ) );
$text .= "\n<!-- \n$limitReport-->\n";
}
function getFunctionLang() {
global $wgLang, $wgContLang;
- return $this->mOptions->getInterfaceMessage() ? $wgLang : $wgContLang;
+
+ $target = $this->mOptions->getTargetLanguage();
+ if ( $target !== null ) {
+ return $target;
+ } else {
+ 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
+ * in the text with a random marker and returns the next text. The output
* parameter $matches will be an associative array filled with data in
* the form:
* 'UNIQ-xxxxx' => array(
$inside = $p[4];
}
- $marker = "$uniq_prefix-$element-" . sprintf('%08X', $n++) . $this->mMarkerSuffix;
+ $marker = "$uniq_prefix-$element-" . sprintf('%08X', $n++) . self::MARKER_SUFFIX;
$stripped .= $marker;
if ( $close === '/>' ) {
* @private
*/
function insertStripItem( $text ) {
- static $n = 0;
- $rnd = "{$this->mUniqPrefix}-item-$n-{$this->mMarkerSuffix}";
- ++$n;
+ $rnd = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
+ $this->mMarkerIndex++;
$this->mStripState->general->setPair( $rnd, $text );
return $rnd;
}
/**
* Use the HTML tidy PECL extension to use the tidy library in-process,
- * saving the overhead of spawning a new process.
+ * saving the overhead of spawning a new process.
*
* 'pear install tidy' should be able to compile the extension module.
*
$cleansource = tidy_get_output( $tidy );
}
if ( $wgDebugTidy && $tidy->getStatus() > 0 ) {
- $cleansource .= "<!--\nTidy reports:\n" .
- str_replace( '-->', '-->', $tidy->errorBuffer ) .
+ $cleansource .= "<!--\nTidy reports:\n" .
+ str_replace( '-->', '-->', $tidy->errorBuffer ) .
"\n-->";
}
} else if ( count ( $td_history ) == 0 ) {
// Don't do any of the following
continue;
- } else if ( substr ( $line , 0 , 2 ) == '|}' ) {
+ } else if ( substr ( $line , 0 , 2 ) == '|}' ) {
// We are ending a table
$line = '</table>' . substr ( $line , 2 );
$last_tag = array_pop ( $last_tag_history );
$text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
- $text = $this->stripToc( $text );
- $this->stripNoGallery( $text );
+ $text = $this->doDoubleUnderscore( $text );
$text = $this->doHeadings( $text );
if($this->mOptions->getUseDynamicDates()) {
- $df =& DateFormatter::getInstance();
+ $df = DateFormatter::getInstance();
$text = $df->reformat( $this->mOptions->getDateFormat(), $text );
}
$text = $this->doAllQuotes( $text );
' ' => '',
'x' => 'X',
));
- $titleObj = SpecialPage::getTitleFor( 'Booksources' );
+ $titleObj = SpecialPage::getTitleFor( 'Booksources', $num );
$text = '<a href="' .
- $titleObj->escapeLocalUrl( "isbn=$num" ) .
+ $titleObj->escapeLocalUrl() .
"\" class=\"internal\">ISBN $isbn</a>";
} else {
if ( substr( $m[0], 0, 3 ) == 'RFC' ) {
}
if( is_null( $this->mTitle ) ) {
+ wfProfileOut( $fname );
+ wfProfileOut( $fname.'-setup' );
throw new MWException( __METHOD__.": \$this->mTitle is null\n" );
}
$nottalk = !$this->mTitle->isTalkPage();
# should be external links.
if (preg_match('/^\b(?:' . wfUrlProtocols() . ')/', $m[1])) {
$s .= $prefix . '[[' . $line ;
+ wfProfileOut( "$fname-misc" );
continue;
}
# Special and Media are pseudo-namespaces; no pages actually exist in them
if( $ns == NS_MEDIA ) {
- $link = $sk->makeMediaLinkObj( $nt, $text );
+ # Give extensions a chance to select the file revision for us
+ $skip = $time = false;
+ wfRunHooks( 'BeforeParserMakeImageLinkObj', array( &$this, &$nt, &$skip, &$time ) );
+ if ( $skip ) {
+ $link = $sk->makeLinkObj( $nt );
+ } else {
+ $link = $sk->makeMediaLinkObj( $nt, $text, $time );
+ }
# Cloak with NOPARSE to avoid replacement in replaceExternalLinks
$s .= $prefix . $this->armorLinks( $link ) . $trail;
$this->mOutput->addImage( $nt->getDBkey() );
$oldtz = getenv( 'TZ' );
putenv( 'TZ='.$wgLocaltimezone );
}
-
+
wfSuppressWarnings(); // E_STRICT system time bitching
$localTimestamp = date( 'YmdHis', $ts );
$localMonth = date( 'm', $ts );
/**
* Preprocess some wikitext and return the document tree.
- * This is the ghost of replace_variables().
+ * This is the ghost of replace_variables().
*
* @param string $text The text to parse
* @param integer flags Bitwise combination of:
- * self::PTD_FOR_INCLUSION Handle <noinclude>/<includeonly> as if the text is being
- * included. Default is to assume a direct page view.
+ * self::PTD_FOR_INCLUSION Handle <noinclude>/<includeonly> as if the text is being
+ * included. Default is to assume a direct page view.
*
* The generated DOM tree must depend only on the input text and the flags.
- * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899.
+ * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899.
*
- * Any flag added to the $flags parameter here, or any other parameter liable to cause a
- * change in the DOM tree for a given text, must be passed through the section identifier
- * in the section edit link and thus back to extractSections().
+ * Any flag added to the $flags parameter here, or any other parameter liable to cause a
+ * change in the DOM tree for a given text, must be passed through the section identifier
+ * in the section edit link and thus back to extractSections().
*
- * The output of this function is currently only cached in process memory, but a persistent
- * cache may be implemented at a later date which takes further advantage of these strict
+ * The output of this function is currently only cached in process memory, but a persistent
+ * cache may be implemented at a later date which takes further advantage of these strict
* dependency requirements.
*
* @private
return $dom;
}
- /*
+ /*
* Return a three-element array: leading whitespace, string contents, trailing whitespace
*/
public static function splitWhitespace( $s ) {
# Title object, where $text came from
$title = NULL;
- # $part1 is the bit before the first |, and must contain only title characters.
- # Various prefixes will be stripped from it later.
+ # $part1 is the bit before the first |, and must contain only title characters.
+ # Various prefixes will be stripped from it later.
$titleWithSpaces = $frame->expand( $piece['title'] );
$part1 = trim( $titleWithSpaces );
$titleText = false;
# SUBST
wfProfileIn( __METHOD__.'-modifiers' );
if ( !$found ) {
- $mwSubst =& MagicWord::get( 'subst' );
+ $mwSubst = MagicWord::get( 'subst' );
if ( $mwSubst->matchStartAndRemove( $part1 ) xor $this->ot['wiki'] ) {
# One of two possibilities is true:
# 1) Found SUBST but not in the PST phase
# MSG, MSGNW and RAW
if ( !$found ) {
# Check for MSGNW:
- $mwMsgnw =& MagicWord::get( 'msgnw' );
+ $mwMsgnw = MagicWord::get( 'msgnw' );
if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
$nowiki = true;
} else {
# Remove obsolete MSG:
- $mwMsg =& MagicWord::get( 'msg' );
+ $mwMsg = MagicWord::get( 'msg' );
$mwMsg->matchStartAndRemove( $part1 );
}
# Check for RAW:
- $mwRaw =& MagicWord::get( 'raw' );
+ $mwRaw = MagicWord::get( 'raw' );
if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
$forceRawInterwiki = true;
}
$allArgs = array_merge( $initialArgs, $funcArgs );
}
+ # Workaround for PHP bug 35229 and similar
+ if ( !is_callable( $callback ) ) {
+ throw new MWException( "Tag hook for $name is not callable\n" );
+ }
$result = call_user_func_array( $callback, $allArgs );
$found = true;
elseif ( is_string( $text ) && !$piece['lineStart'] && preg_match('/^(?:{\\||:|;|#|\*)/', $text)) /*}*/{
$text = "\n" . $text;
}
-
+
if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
# Error, oversize inclusion
- $text = "[[$originalTitle]]" .
+ $text = "[[$originalTitle]]" .
$this->insertStripItem( '<!-- WARNING: template omitted, post-expand include size too large -->' );
}
function getTemplateDom( $title ) {
$cacheTitle = $title;
$titleText = $title->getPrefixedDBkey();
-
+
if ( isset( $this->mTplRedirCache[$titleText] ) ) {
list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
$title = Title::makeTitle( $ns, $dbk );
$this->mTplDomCache[ $titleText ] = $dom;
if (! $title->equals($cacheTitle)) {
- $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
+ $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
array( $title->getNamespace(),$cdb = $title->getDBkey() );
}
$text = $skip = false;
$finalTitle = $title;
$deps = array();
-
+
// Loop to fetch the article, with up to 1 redirect
for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
# Give extensions a chance to select the revision instead
$id = false; // Assume current
wfRunHooks( 'BeforeParserFetchTemplateAndtitle', array( false, &$title, &$skip, &$id ) );
-
+
if( $skip ) {
$text = false;
$deps[] = array(
$rev = $id ? Revision::newFromId( $id ) : Revision::newFromTitle( $title );
$rev_id = $rev ? $rev->getId() : 0;
- $deps[] = array(
- 'title' => $title,
- 'page_id' => $title->getArticleID(),
+ $deps[] = array(
+ 'title' => $title,
+ 'page_id' => $title->getArticleID(),
'rev_id' => $rev_id );
if( $rev ) {
$argName = trim( $nameWithSpaces );
$object = false;
$text = $frame->getArgument( $argName );
- if ( $text === false && ( $this->ot['html'] || $this->ot['pre'] ) && $parts->getLength() > 0 ) {
+ if ( $text === false && $parts->getLength() > 0
+ && (
+ $this->ot['html']
+ || $this->ot['pre']
+ || ( $this->ot['wiki'] && $frame->isTemplate() )
+ )
+ ) {
# No match in frame, use the supplied default
$object = $parts->item( 0 )->getChildren();
}
*/
function extensionSubstitution( $params, $frame ) {
global $wgRawHtml, $wgContLang;
- static $n = 1;
$name = $frame->expand( $params['name'] );
$attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
$content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
- $marker = "{$this->mUniqPrefix}-$name-" . sprintf('%08X', $n++) . $this->mMarkerSuffix;
-
+ $marker = "{$this->mUniqPrefix}-$name-" . sprintf('%08X', $this->mMarkerIndex++) . self::MARKER_SUFFIX;
+
if ( $this->ot['html'] ) {
$name = strtolower( $name );
break;
default:
if( isset( $this->mTagHooks[$name] ) ) {
+ # Workaround for PHP bug 35229 and similar
+ if ( !is_callable( $this->mTagHooks[$name] ) ) {
+ throw new MWException( "Tag hook for $name is not callable\n" );
+ }
$output = call_user_func_array( $this->mTagHooks[$name],
array( $content, $attributes, $this ) );
} else {
}
/**
- * Detect __NOGALLERY__ magic word and set a placeholder
+ * Increment the expensive function count
+ *
+ * @return boolean False if the limit has been exceeded
*/
- function stripNoGallery( &$text ) {
- # if the string __NOGALLERY__ (not case-sensitive) occurs in the HTML,
- # do not add TOC
- $mw = MagicWord::get( 'nogallery' );
- $this->mOutput->mNoGallery = $mw->matchAndRemove( $text ) ;
+ function incrementExpensiveFunctionCount() {
+ global $wgExpensiveParserFunctionLimit;
+ $this->mExpensiveFunctionCount++;
+ if($this->mExpensiveFunctionCount <= $wgExpensiveParserFunctionLimit) {
+ return true;
+ }
+ return false;
}
/**
- * Find the first __TOC__ magic word and set a <!--MWTOC-->
- * placeholder that will then be replaced by the real TOC in
- * ->formatHeadings, this works because at this points real
- * comments will have already been discarded by the sanitizer.
- *
- * Any additional __TOC__ magic words left over will be discarded
- * as there can only be one TOC on the page.
+ * Strip double-underscore items like __NOGALLERY__ and __NOTOC__
+ * Fills $this->mDoubleUnderscores, returns the modified text
*/
- function stripToc( $text ) {
- # if the string __NOTOC__ (not case-sensitive) occurs in the HTML,
- # do not add TOC
- $mw = MagicWord::get( 'notoc' );
- if( $mw->matchAndRemove( $text ) ) {
- $this->mShowToc = false;
- }
-
+ function doDoubleUnderscore( $text ) {
+ // The position of __TOC__ needs to be recorded
$mw = MagicWord::get( 'toc' );
if( $mw->match( $text ) ) {
$this->mShowToc = true;
// Only keep the first one.
$text = $mw->replace( '', $text );
}
+
+ // Now match and remove the rest of them
+ $mwa = MagicWord::getDoubleUnderscoreArray();
+ $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
+
+ if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
+ $this->mOutput->mNoGallery = true;
+ }
+ if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
+ $this->mShowToc = false;
+ }
+ if ( isset( $this->mDoubleUnderscores['hiddencat'] ) && $this->mTitle->getNamespace() == NS_CATEGORY ) {
+ $this->mOutput->setProperty( 'hiddencat', 'y' );
+
+ $containerCategory = Title::makeTitleSafe( NS_CATEGORY, wfMsgForContent( 'hidden-category-category' ) );
+ if ( $containerCategory ) {
+ $this->mOutput->addCategory( $containerCategory->getDBkey(), $this->getDefaultSort() );
+ } else {
+ wfDebug( __METHOD__.": [[MediaWiki:hidden-category-category]] is not a valid title!\n" );
+ }
+ }
return $text;
}
}
# Inhibit editsection links if requested in the page
- $esw =& MagicWord::get( 'noeditsection' );
- if( $esw->matchAndRemove( $text ) ) {
+ if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
$showEditLink = 0;
}
# Allow user to stipulate that a page should have a "new section"
# link added via __NEWSECTIONLINK__
- $mw =& MagicWord::get( 'newsectionlink' );
- if( $mw->matchAndRemove( $text ) )
+ if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
$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( 'forcetoc' );
- if ($mw->matchAndRemove( $text ) ) {
+ if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
$this->mShowToc = true;
$enoughToc = true;
}
$prevlevel = 0;
$toclevel = 0;
$prevtoclevel = 0;
- $markerRegex = "{$this->mUniqPrefix}-h-(\d+)-{$this->mMarkerSuffix}";
+ $markerRegex = "{$this->mUniqPrefix}-h-(\d+)-" . self::MARKER_SUFFIX;
$baseTitleText = $this->mTitle->getPrefixedDBkey();
$tocraw = array();
if($prevtoclevel < $wgMaxTocLevel) {
# Unindent only if the previous toc level was shown :p
$toc .= $sk->tocUnindent( $prevtoclevel - $toclevel );
+ $prevtoclevel = $toclevel;
} else {
$toc .= $sk->tocLineEnd();
}
# Save headline for section edit hint before it's escaped
$headlineHint = $safeHeadline;
$safeHeadline = Sanitizer::escapeId( $safeHeadline );
+ # HTML names must be case-insensitively unique (bug 10721)
+ $arrayKey = strtolower( $safeHeadline );
+
+ # XXX : Is $refers[$headlineCount] ever accessed, actually ?
$refers[$headlineCount] = $safeHeadline;
# count how many in assoc. array so we can track dupes in anchors
- isset( $refers[$safeHeadline] ) ? $refers[$safeHeadline]++ : $refers[$safeHeadline] = 1;
- $refcount[$headlineCount] = $refers[$safeHeadline];
+ isset( $refers[$arrayKey] ) ? $refers[$arrayKey]++ : $refers[$arrayKey] = 1;
+ $refcount[$headlineCount] = $refers[$arrayKey];
# Don't number the heading if it is the only one (looks silly)
if( $doNumberHeadings && count( $matches[3] ) > 1) {
# give headline the correct <h#> tag
if( $showEditLink && $sectionIndex !== false ) {
if( $isTemplate ) {
- # Put a T flag in the section identifier, to indicate to extractSections()
+ # Put a T flag in the section identifier, to indicate to extractSections()
# that sections inside <includeonly> should be counted.
$editlink = $sk->editSectionLinkForOther($titleText, "T-$sectionIndex");
} else {
if( $numVisible < 1 ) {
$enoughToc = false;
}
-
+
if( $enoughToc ) {
if( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
$toc .= $sk->tocUnindent( $prevtoclevel - 1 );
* the database, we use $wgContLang here in order to give
* everyone the same signature and use the default one rather
* than the one selected in each user's preferences.
+ *
+ * (see also bug 12815)
*/
+ $ts = $this->mOptions->getTimestamp();
+ $tz = 'UTC';
if ( isset( $wgLocaltimezone ) ) {
+ $unixts = wfTimestamp( TS_UNIX, $ts );
$oldtz = getenv( 'TZ' );
putenv( 'TZ='.$wgLocaltimezone );
- }
- $d = $wgContLang->timeanddate( $this->mOptions->getTimestamp(), false, false) .
- ' (' . date( 'T' ) . ')';
- if ( isset( $wgLocaltimezone ) ) {
+ $ts = date( 'YmdHis', $unixts );
+ $tz = date( 'T', $unixts ); # might vary on DST changeover!
putenv( 'TZ='.$oldtz );
}
+ $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tz)";
# 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, $this->mStripState, false, array( 'gallery' ) );
-
# Signatures
$sigText = $this->getUserSig( $user );
$text = strtr( $text, array(
*/
function getUserSig( &$user ) {
global $wgMaxSigChars;
-
+
$username = $user->getName();
$nickname = $user->getOption( 'nickname' );
$nickname = $nickname === '' ? $username : $nickname;
-
+
if( mb_strlen( $nickname ) > $wgMaxSigChars ) {
$nickname = $username;
wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
$tag = strtolower( $tag );
$oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
$this->mTagHooks[$tag] = $callback;
- $this->mStripList[] = $tag;
+ if( !in_array( $tag, $this->mStripList ) ) {
+ $this->mStripList[] = $tag;
+ }
return $oldVal;
}
$colours = array();
$linkcolour_ids = array();
$sk = $this->mOptions->getSkin();
- $linkCache =& LinkCache::singleton();
+ $linkCache = LinkCache::singleton();
if ( !empty( $this->mLinkHolders['namespaces'] ) ) {
wfProfileIn( $fname.'-check' );
# Not in the link cache, add it to the query
if ( !isset( $current ) ) {
$current = $ns;
- $query = "SELECT page_id, page_namespace, page_title";
- if ( $threshold > 0 ) {
- $query .= ', page_len, page_is_redirect';
- }
+ $query = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len";
$query .= " FROM $page WHERE (page_namespace=$ns AND page_title IN(";
} elseif ( $current != $ns ) {
$current = $ns;
while ( $s = $dbr->fetchObject($res) ) {
$title = Title::makeTitle( $s->page_namespace, $s->page_title );
$pdbk = $title->getPrefixedDBkey();
- $linkCache->addGoodLinkObj( $s->page_id, $title );
+ $linkCache->addGoodLinkObj( $s->page_id, $title, $s->page_len, $s->page_is_redirect );
$this->mOutput->addLink( $title, $s->page_id );
- $colours[$pdbk] = $sk->getLinkColour( $s, $threshold );
+ $colours[$pdbk] = $sk->getLinkColour( $title, $threshold );
//add id to the extension todolist
$linkcolour_ids[$s->page_id] = $pdbk;
}
// construct query
$titleClause = $linkBatch->constructSet('page', $dbr);
- $variantQuery = "SELECT page_id, page_namespace, page_title";
- if ( $threshold > 0 ) {
- $variantQuery .= ', page_len, page_is_redirect';
- }
+ $variantQuery = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len";
$variantQuery .= " FROM $page WHERE $titleClause";
if ( $options & RLH_FOR_UPDATE ) {
$holderKeys = array();
if(isset($variantMap[$varPdbk])){
$holderKeys = $variantMap[$varPdbk];
- $linkCache->addGoodLinkObj( $s->page_id, $variantTitle );
+ $linkCache->addGoodLinkObj( $s->page_id, $variantTitle, $s->page_len, $s->page_is_redirect );
$this->mOutput->addLink( $variantTitle, $s->page_id );
}
// set pdbk and colour
$pdbks[$key] = $varPdbk;
- $colours[$varPdbk] = $sk->getLinkColour( $s, $threshold );
+ $colours[$varPdbk] = $sk->getLinkColour( $variantTitle, $threshold );
$linkcolour_ids[$s->page_id] = $pdbk;
}
wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
if( isset( $params['heights'] ) ) {
$ig->setHeights( $params['heights'] );
}
-
+
wfRunHooks( 'BeforeParserrenderImageGallery', array( &$this, &$ig ) );
$lines = explode( "\n", $text );
// Initialise static lists
static $internalParamNames = array(
'horizAlign' => array( 'left', 'right', 'center', 'none' ),
- 'vertAlign' => array( 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
+ 'vertAlign' => array( 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
'bottom', 'text-bottom' ),
- 'frame' => array( 'thumbnail', 'manualthumb', 'framed', 'frameless',
+ 'frame' => array( 'thumbnail', 'manualthumb', 'framed', 'frameless',
'upright', 'border' ),
);
static $internalParamMap;
* Parse image options text and use it to make an image
*/
function makeImage( $title, $options ) {
- # @TODO: let the MediaHandler specify its transform parameters
- #
# Check if the options text is of the form "options|alt text"
# Options are:
# * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
# * middle
# * bottom
# * text-bottom
-
+
$parts = array_map( 'trim', explode( '|', $options) );
$sk = $this->mOptions->getSkin();
# Process the input parameters
$caption = '';
- $params = array( 'frame' => array(), 'handler' => array(),
+ $params = array( 'frame' => array(), 'handler' => array(),
'horizAlign' => array(), 'vertAlign' => array() );
foreach( $parts as $part ) {
list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
- if ( isset( $paramMap[$magicName] ) ) {
+ $validated = false;
+ if( isset( $paramMap[$magicName] ) ) {
list( $type, $paramName ) = $paramMap[$magicName];
- $params[$type][$paramName] = $value;
-
+
// Special case; width and height come in one variable together
if( $type == 'handler' && $paramName == 'width' ) {
$m = array();
- if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $value, $m ) ) {
- $params[$type]['width'] = intval( $m[1] );
- $params[$type]['height'] = intval( $m[2] );
+ # (bug 13500) In both cases (width/height and width only),
+ # permit trailing "px" for backward compatibility.
+ if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
+ $width = intval( $m[1] );
+ $height = intval( $m[2] );
+ if ( $handler->validateParam( 'width', $width ) ) {
+ $params[$type]['width'] = $width;
+ $validated = true;
+ }
+ if ( $handler->validateParam( 'height', $height ) ) {
+ $params[$type]['height'] = $height;
+ $validated = true;
+ }
+ } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
+ $width = intval( $value );
+ if ( $handler->validateParam( 'width', $width ) ) {
+ $params[$type]['width'] = $width;
+ $validated = true;
+ }
+ } // else no validation -- bug 13436
+ } else {
+ if ( $type == 'handler' ) {
+ # Validate handler parameter
+ $validated = $handler->validateParam( $paramName, $value );
} else {
- $params[$type]['width'] = intval( $value );
+ # Validate internal parameters
+ switch( $paramName ) {
+ case "manualthumb":
+ /// @fixme - possibly check validity here?
+ /// downstream behavior seems odd with missing manual thumbs.
+ $validated = true;
+ break;
+ default:
+ // Most other things appear to be empty or numeric...
+ $validated = ( $value === false || is_numeric( trim( $value ) ) );
+ }
+ }
+
+ if ( $validated ) {
+ $params[$type][$paramName] = $value;
}
}
- } else {
+ }
+ if ( !$validated ) {
$caption = $part;
}
}
$params['frame']['valign'] = key( $params['vertAlign'] );
}
- # Validate the handler parameters
- if ( $handler ) {
- foreach ( $params['handler'] as $name => $value ) {
- if ( !$handler->validateParam( $name, $value ) ) {
- unset( $params['handler'][$name] );
- }
- }
- }
-
# Strip bad stuff out of the alt text
$alt = $this->replaceLinkHoldersText( $caption );
$params['frame']['alt'] = $alt;
$params['frame']['caption'] = $caption;
+ wfRunHooks( 'ParserMakeImageParams', array( $title, $file, &$params ) );
+
# Linker does the rest
- $ret = $sk->makeImageLink2( $title, $file, $params['frame'], $params['handler'] );
+ $ret = $sk->makeImageLink2( $title, $file, $params['frame'], $params['handler'], $time );
# Give the handler a chance to modify the parser object
if ( $handler ) {
* <flag1> - <flag2> - ... - <section number>
*
* Currently the only recognised flag is "T", which means the target section number
- * was derived during a template inclusion parse, in other words this is a template
- * section edit link. If no flags are given, it was an ordinary section edit link.
- * This flag is required to avoid a section numbering mismatch when a section is
+ * was derived during a template inclusion parse, in other words this is a template
+ * section edit link. If no flags are given, it was an ordinary section edit link.
+ * This flag is required to avoid a section numbering mismatch when a section is
* enclosed by <includeonly> (bug 6563).
*
- * The section number 0 pulls the text before the first heading; other numbers will
- * pull the given section along with its lower-level subsections. If the section is
+ * The section number 0 pulls the text before the first heading; other numbers will
+ * pull the given section along with its lower-level subsections. If the section is
* not found, $mode=get will return $newtext, and $mode=replace will return $text.
*
* @param string $mode One of "get" or "replace"
$this->setTitle( $wgTitle ); // not generally used but removes an ugly failure mode
$this->mOptions = new ParserOptions;
$this->setOutputType( self::OT_WIKI );
- $curIndex = 0;
$outText = '';
$frame = $this->getPreprocessor()->newFrame();
// Section zero doesn't nest, level=big
$targetLevel = 1000;
} else {
- while ( $node ) {
- if ( $node->getName() == 'h' ) {
- if ( $curIndex + 1 == $sectionIndex ) {
+ while ( $node ) {
+ if ( $node->getName() == 'h' ) {
+ $bits = $node->splitHeading();
+ if ( $bits['i'] == $sectionIndex ) {
+ $targetLevel = $bits['level'];
break;
}
- $curIndex++;
}
if ( $mode == 'replace' ) {
$outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
}
$node = $node->getNextSibling();
}
- if ( $node ) {
- $bits = $node->splitHeading();
- $targetLevel = $bits['level'];
- }
}
if ( !$node ) {
// Find the end of the section, including nested sections
do {
if ( $node->getName() == 'h' ) {
- $curIndex++;
$bits = $node->splitHeading();
$curLevel = $bits['level'];
- if ( $curIndex != $sectionIndex && $curLevel <= $targetLevel ) {
+ if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
break;
}
}
}
$node = $node->getNextSibling();
} while ( $node );
-
+
// Write out the remainder (in replace mode only)
if ( $mode == 'replace' ) {
// Output the replacement text
- // Add two newlines on -- trailing whitespace in $newText is conventionally
+ // Add two newlines on -- trailing whitespace in $newText is conventionally
// stripped by the editor, so we need both newlines to restore the paragraph gap
$outText .= $newText . "\n\n";
while ( $node ) {
if ( is_string( $outText ) ) {
// Re-insert stripped tags
- $outText = trim( $this->mStripState->unstripBoth( $outText ) );
+ $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
}
return $outText;
}
/**
- * Try to guess the section anchor name based on a wikitext fragment
- * presumably extracted from a heading, for example "Header" from
+ * Try to guess the section anchor name based on a wikitext fragment
+ * presumably extracted from a heading, for example "Header" from
* "== Header ==".
*/
public function guessSectionNameFromWikiText( $text ) {
/**
* Strips a text string of wikitext for use in a section anchor
- *
+ *
* Accepts a text string and then removes all wikitext from the
* string and leaves only the resultant text (i.e. the result of
* [[User:WikiSysop|Sysop]] would be "Sysop" and the result of
* [[User:WikiSysop]] would be "User:WikiSysop") - this is intended
* to create valid section anchors by mimicing the output of the
* parser when headings are parsed.
- *
+ *
* @param $text string Text string to be stripped of wikitext
* for use in a Section anchor
* @return Filtered text string
# Strip internal link markup
$text = preg_replace('/\[\[:?([^[|]+)\|([^[]+)\]\]/','$2',$text);
$text = preg_replace('/\[\[:?([^[]+)\|?\]\]/','$1',$text);
-
+
# Strip external link markup (FIXME: Not Tolerant to blank link text
# I.E. [http://www.mediawiki.org] will render as [1] or something depending
# on how many empty links there are on the page - need to figure that out.
$text = preg_replace('/\[(?:' . wfUrlProtocols() . ')([^ ]+?) ([^[]+)\]/','$2',$text);
-
+
# Parse wikitext quotes (italics & bold)
$text = $this->doQuotes($text);
-
+
# Strip HTML tags
$text = StringUtils::delimiterReplace( '<', '>', '', $text );
return $text;
break;
} else {
$out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
- $markerEnd = strpos( $s, $this->mMarkerSuffix, $markerStart );
+ $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
if ( $markerEnd === false ) {
$out .= substr( $s, $markerStart );
break;
} else {
- $markerEnd += strlen( $this->mMarkerSuffix );
+ $markerEnd += strlen( self::MARKER_SUFFIX );
$out .= substr( $s, $markerStart, $markerEnd - $markerStart );
$i = $markerEnd;
}
class OnlyIncludeReplacer {
var $output = '';
- function replace( $matches ) {
+ function replace( $matches ) {
if ( substr( $matches[1], -1 ) == "\n" ) {
$this->output .= substr( $matches[1], 0, -1 );
} else {
}
}
}
-