* Enable category names to be written in variants (use single linkbatch for both...
[lhc/web/wiklou.git] / includes / Parser.php
index 3ebe597..ad39cbd 100644 (file)
  */
 define( 'MW_PARSER_VERSION', '1.6.1' );
 
-/**
- * Variable substitution O(N^2) attack
- *
- * Without countermeasures, it would be possible to attack the parser by saving
- * a page filled with a large number of inclusions of large pages. The size of
- * the generated page would be proportional to the square of the input size.
- * Hence, we limit the number of inclusions of any given page, thus bringing any
- * attack back to O(N).
- */
-
-define( 'MAX_INCLUDE_REPEAT', 100 );
-define( 'MAX_INCLUDE_SIZE', 1000000 ); // 1 Million
-
 define( 'RLH_FOR_UPDATE', 1 );
 
 # Allowed values for $mOutputType
 define( 'OT_HTML', 1 );
 define( 'OT_WIKI', 2 );
 define( 'OT_MSG' , 3 );
+define( 'OT_PREPROCESS', 4 );
 
 # Flags for setFunctionHook
 define( 'SFH_NO_HASH', 1 );
@@ -74,13 +62,15 @@ define( 'MW_COLON_STATE_COMMENTDASHDASH', 7 );
  * Processes wiki markup
  *
  * <pre>
- * There are three main entry points into the Parser class:
+ * There are four main entry points into the Parser class:
  * parse()
  *   produces HTML output
  * preSaveTransform().
  *   produces altered wiki markup.
  * transformMsg()
  *   performs brace substitution on MediaWiki messages
+ * preprocess()
+ *   removes HTML comments and expands templates
  *
  * Globals used:
  *    objects:   $wgLang, $wgContLang
@@ -90,7 +80,8 @@ define( 'MW_COLON_STATE_COMMENTDASHDASH', 7 );
  * settings:
  *  $wgUseTex*, $wgUseDynamicDates*, $wgInterwikiMagic*,
  *  $wgNamespacesWithSubpages, $wgAllowExternalImages*,
- *  $wgLocaltimezone, $wgAllowSpecialInclusion*
+ *  $wgLocaltimezone, $wgAllowSpecialInclusion*,
+ *  $wgMaxArticleSize*
  *
  *  * only within ParserOptions
  * </pre>
@@ -106,9 +97,10 @@ class Parser
        var $mTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables;
 
        # Cleared with clearState():
-       var $mOutput, $mAutonumber, $mDTopen, $mStripState = array();
+       var $mOutput, $mAutonumber, $mDTopen, $mStripState;
        var $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
        var $mInterwikiLinkHolders, $mLinkHolders, $mUniqPrefix;
+       var $mIncludeSizes;
        var $mTemplates,        // cache of already loaded templates, avoids
                                // multiple SQL queries for the same string
            $mTemplatePath;     // stores an unsorted hash of all the templates already loaded
@@ -119,7 +111,10 @@ class Parser
        var $mOptions,      // ParserOptions object
                $mTitle,        // Title context, used for self-link rendering and similar things
                $mOutputType,   // Output type, one of the OT_xxx constants
-               $mRevisionId;   // ID to display in {{REVISIONID}} tags
+               $ot,            // Shortcut alias, see setOutputType()
+               $mRevisionId,   // ID to display in {{REVISIONID}} tags
+               $mRevisionTimestamp, // The timestamp of the specified revision ID
+               $mRevIdForTs;   // The revision ID which was used to fetch the timestamp  
 
        /**#@-*/
 
@@ -148,6 +143,7 @@ class Parser
 
                $this->setHook( 'pre', array( $this, 'renderPreTag' ) );
 
+               $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 );
@@ -167,6 +163,10 @@ class Parser
                $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 );
+               $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' ) );
 
                if ( $wgAllowDisplayTitle ) {
                        $this->setFunctionHook( 'displaytitle', array( 'CoreParserFunctions', 'displaytitle' ), SFH_NO_HASH );
@@ -174,12 +174,11 @@ class Parser
                if ( $wgAllowSlowParserFunctions ) {
                        $this->setFunctionHook( 'pagesinnamespace', array( 'CoreParserFunctions', 'pagesinnamespace' ), SFH_NO_HASH );
                }
-               
-               $this->initialiseVariables();
 
+               $this->initialiseVariables();
                $this->mFirstCall = false;
                wfProfileOut( __METHOD__ );
-       }               
+       }
 
        /**
         * Clear Parser state
@@ -187,6 +186,7 @@ class Parser
         * @private
         */
        function clearState() {
+               wfProfileIn( __METHOD__ );
                if ( $this->mFirstCall ) {
                        $this->firstCallInit();
                }
@@ -195,7 +195,7 @@ class Parser
                $this->mLastSection = '';
                $this->mDTopen = false;
                $this->mIncludeCount = array();
-               $this->mStripState = array();
+               $this->mStripState = new StripState;
                $this->mArgStack = array();
                $this->mInPre = false;
                $this->mInterwikiLinkHolders = array(
@@ -209,7 +209,7 @@ class Parser
                        'texts' => array(),
                        'titles' => array()
                );
-               $this->mRevisionId = null;
+               $this->mRevisionTimestamp = $this->mRevisionId = null;
                
                /**
                 * Prefix for temporary replacement strings for the multipass parser.
@@ -226,8 +226,25 @@ class Parser
 
                $this->mShowToc = true;
                $this->mForceTocPosition = false;
+               $this->mIncludeSizes = array(
+                       'pre-expand' => 0,
+                       'post-expand' => 0,
+                       'arg' => 0
+               );
 
                wfRunHooks( 'ParserClearState', array( &$this ) );
+               wfProfileOut( __METHOD__ );
+       }
+
+       function setOutputType( $ot ) {
+               $this->mOutputType = $ot;
+               // Shortcut alias
+               $this->ot = array(
+                       'html' => $ot == OT_HTML,
+                       'wiki' => $ot == OT_WIKI,
+                       'msg' => $ot == OT_MSG,
+                       'pre' => $ot == OT_PREPROCESS,
+               );
        }
 
        /**
@@ -235,7 +252,7 @@ class Parser
         *
         * @public
         */
-       function UniqPrefix() {
+       function uniqPrefix() {
                return $this->mUniqPrefix;
        }
 
@@ -259,7 +276,8 @@ class Parser
                 */
 
                global $wgUseTidy, $wgAlwaysUseTidy, $wgContLang;
-               $fname = 'Parser::parse';
+               $fname = 'Parser::parse-' . wfGetCaller();
+               wfProfileIn( __METHOD__ );
                wfProfileIn( $fname );
 
                if ( $clearState ) {
@@ -269,28 +287,17 @@ class Parser
                $this->mOptions = $options;
                $this->mTitle =& $title;
                $oldRevisionId = $this->mRevisionId;
+               $oldRevisionTimestamp = $this->mRevisionTimestamp;
                if( $revid !== null ) {
                        $this->mRevisionId = $revid;
+                       $this->mRevisionTimestamp = null;
                }
-               $this->mOutputType = OT_HTML;
-
-               //$text = $this->strip( $text, $this->mStripState );
-               // VOODOO MAGIC FIX! Sometimes the above segfaults in PHP5.
-               $x =& $this->mStripState;
-
-               wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$x ) );
-               $text = $this->strip( $text, $x );
-               wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$x ) );
-
-               # Hook to suspend the parser in this state
-               if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$x ) ) ) {
-                       wfProfileOut( $fname );
-                       return $text ;
-               }
-
+               $this->setOutputType( OT_HTML );
+               wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
+               $text = $this->strip( $text, $this->mStripState );
+               wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
                $text = $this->internalParse( $text );
-
-               $text = $this->unstrip( $text, $this->mStripState );
+               $text = $this->mStripState->unstripGeneral( $text );
 
                # Clean up special characters, only run once, next-to-last before doBlockLevels
                $fixtags = array(
@@ -313,7 +320,7 @@ class Parser
                # Side-effects: this calls $this->mOutput->setTitleText()
                $text = $wgContLang->parserConvert( $text, $this );
 
-               $text = $this->unstripNoWiki( $text, $this->mStripState );
+               $text = $this->mStripState->unstripNoWiki( $text );
 
                wfRunHooks( 'ParserBeforeTidy', array( &$this, &$text ) );
 
@@ -351,13 +358,61 @@ class Parser
 
                wfRunHooks( 'ParserAfterTidy', array( &$this, &$text ) );
 
+               # Information on include size limits, for the benefit of users who try to skirt them
+               if ( max( $this->mIncludeSizes ) > 1000 ) {
+                       $max = $this->mOptions->getMaxIncludeSize();
+                       $text .= "<!-- \n" .
+                               "Pre-expand include size: {$this->mIncludeSizes['pre-expand']} bytes\n" .
+                               "Post-expand include size: {$this->mIncludeSizes['post-expand']} bytes\n" .
+                               "Template argument size: {$this->mIncludeSizes['arg']} bytes\n" .
+                               "Maximum: $max bytes\n" .
+                               "-->\n";
+               }
                $this->mOutput->setText( $text );
                $this->mRevisionId = $oldRevisionId;
+               $this->mRevisionTimestamp = $oldRevisionTimestamp;
                wfProfileOut( $fname );
+               wfProfileOut( __METHOD__ );
 
                return $this->mOutput;
        }
 
+       /**
+        * Recursive parser entry point that can be called from an extension tag
+        * hook.
+        */
+       function recursiveTagParse( $text ) {
+               wfProfileIn( __METHOD__ );
+               wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
+               $text = $this->strip( $text, $this->mStripState );
+               wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
+               $text = $this->internalParse( $text );
+               wfProfileOut( __METHOD__ );
+               return $text;
+       }
+
+       /**
+        * Expand templates and variables in the text, producing valid, static wikitext.
+        * Also removes comments.
+        */
+       function preprocess( $text, $title, $options ) {
+               wfProfileIn( __METHOD__ );
+               $this->clearState();
+               $this->setOutputType( OT_PREPROCESS );
+               $this->mOptions = $options;
+               $this->mTitle = $title;
+               wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
+               $text = $this->strip( $text, $this->mStripState );
+               wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
+               if ( $this->mOptions->getRemoveComments() ) {
+                       $text = Sanitizer::removeHTMLcomments( $text );
+               }
+               $text = $this->replaceVariables( $text );
+               $text = $this->mStripState->unstripBoth( $text );
+               wfProfileOut( __METHOD__ );
+               return $text;
+       }
+
        /**
         * Get a random string
         *
@@ -395,8 +450,7 @@ class Parser
         * @static
         */
        function extractTagsAndParams($elements, $text, &$matches, $uniq_prefix = ''){
-               $rand = Parser::getRandomString();
-               $n = 1;
+               static $n = 1;
                $stripped = '';
                $matches = array();
 
@@ -423,7 +477,7 @@ class Parser
                                $inside     = $p[4];
                        }
 
-                       $marker = "$uniq_prefix-$element-$rand" . sprintf('%08X', $n++) . '-QINU';
+                       $marker = "$uniq_prefix-$element-" . sprintf('%08X', $n++) . '-QINU';
                        $stripped .= $marker;
 
                        if ( $close === '/>' ) {
@@ -448,7 +502,7 @@ class Parser
                                        $text = $q[2];
                                }
                        }
-                       
+
                        $matches[$marker] = array( $element,
                                $content,
                                Sanitizer::decodeTagAttributes( $attributes ),
@@ -461,26 +515,27 @@ class Parser
         * Strips and renders nowiki, pre, math, hiero
         * If $render is set, performs necessary rendering operations on plugins
         * Returns the text, and fills an array with data needed in unstrip()
-        * If the $state is already a valid strip state, it adds to the state
+        *
+        * @param StripState $state
         *
         * @param bool $stripcomments when set, HTML comments <!-- like this -->
         *  will be stripped in addition to other tags. This is important
         *  for section editing, where these comments cause confusion when
         *  counting the sections in the wikisource
-        * 
+        *
         * @param array dontstrip contains tags which should not be stripped;
         *  used to prevent stipping of <gallery> when saving (fixes bug 2700)
         *
         * @private
         */
-       function strip( $text, &$state, $stripcomments = false , $dontstrip = array () ) {
+       function strip( $text, $state, $stripcomments = false , $dontstrip = array () ) {
+               global $wgContLang;
+               wfProfileIn( __METHOD__ );
                $render = ($this->mOutputType == OT_HTML);
 
-               # Replace any instances of the placeholders
                $uniq_prefix = $this->mUniqPrefix;
-               #$text = str_replace( $uniq_prefix, wfHtmlEscapeFirst( $uniq_prefix ), $text );
-               $commentState = array();
-               
+               $commentState = new ReplacementArray;
+
                $elements = array_merge(
                        array( 'nowiki', 'gallery' ),
                        array_keys( $this->mTagHooks ) );
@@ -491,13 +546,13 @@ class Parser
                if( $this->mOptions->getUseTeX() ) {
                        $elements[] = 'math';
                }
-               
+
                # Removing $dontstrip tags from $elements list (currently only 'gallery', fixing bug 2700)
                foreach ( $elements AS $k => $v ) {
                        if ( !in_array ( $v , $dontstrip ) ) continue;
                        unset ( $elements[$k] );
                }
-               
+
                $matches = array();
                $text = Parser::extractTagsAndParams( $elements, $text, $matches, $uniq_prefix );
 
@@ -505,6 +560,7 @@ class Parser
                        list( $element, $content, $params, $tag ) = $data;
                        if( $render ) {
                                $tagName = strtolower( $element );
+                               wfProfileIn( __METHOD__."-render-$tagName" );
                                switch( $tagName ) {
                                case '!--':
                                        // Comment
@@ -523,10 +579,10 @@ class Parser
                                        }
                                        // Shouldn't happen otherwise. :)
                                case 'nowiki':
-                                       $output = wfEscapeHTMLTagsOnly( $content );
+                                       $output = Xml::escapeTagsOnly( $content );
                                        break;
                                case 'math':
-                                       $output = MathRenderer::renderMath( $content );
+                                       $output = $wgContLang->armourMath( MathRenderer::renderMath( $content ) );
                                        break;
                                case 'gallery':
                                        $output = $this->renderImageGallery( $content, $params );
@@ -539,14 +595,22 @@ class Parser
                                                throw new MWException( "Invalid call hook $element" );
                                        }
                                }
+                               wfProfileOut( __METHOD__."-render-$tagName" );
                        } else {
                                // Just stripping tags; keep the source
                                $output = $tag;
                        }
+
+                       // Unstrip the output, because unstrip() is no longer recursive so
+                       // it won't do it itself
+                       $output = $state->unstripBoth( $output );
+
                        if( !$stripcomments && $element == '!--' ) {
-                               $commentState[$marker] = $output;
+                               $commentState->setPair( $marker, $output );
+                       } elseif ( $element == 'html' || $element == 'nowiki' ) {
+                               $state->nowiki->setPair( $marker, $output );
                        } else {
-                               $state[$element][$marker] = $output;
+                               $state->general->setPair( $marker, $output );
                        }
                }
 
@@ -556,9 +620,10 @@ class Parser
                # a comment.)
                if ( !$stripcomments ) {
                        // Put them all back and forget them
-                       $text = strtr( $text, $commentState );
+                       $text = $commentState->replace( $text );
                }
 
+               wfProfileOut( __METHOD__ );
                return $text;
        }
 
@@ -567,46 +632,27 @@ class Parser
         *
         * always call unstripNoWiki() after this one
         * @private
+        * @deprecated use $this->mStripState->unstrip()
         */
-       function unstrip( $text, &$state ) {
-               if ( !is_array( $state ) ) {
-                       return $text;
-               }
-
-               $replacements = array();
-               foreach( $state as $tag => $contentDict ) {
-                       if( $tag != 'nowiki' && $tag != 'html' ) {
-                               foreach( $contentDict as $uniq => $content ) {
-                                       $replacements[$uniq] = $content;
-                               }
-                       }
-               }
-               $text = strtr( $text, $replacements );
-
-               return $text;
+       function unstrip( $text, $state ) {
+               return $state->unstripGeneral( $text );
        }
 
        /**
         * Always call this after unstrip() to preserve the order
         *
         * @private
+        * @deprecated use $this->mStripState->unstrip()
         */
-       function unstripNoWiki( $text, &$state ) {
-               if ( !is_array( $state ) ) {
-                       return $text;
-               }
-
-               $replacements = array();
-               foreach( $state as $tag => $contentDict ) {
-                       if( $tag == 'nowiki' || $tag == 'html' ) {
-                               foreach( $contentDict as $uniq => $content ) {
-                                       $replacements[$uniq] = $content;
-                               }
-                       }
-               }
-               $text = strtr( $text, $replacements );
+       function unstripNoWiki( $text, $state ) {
+               return $state->unstripNoWiki( $text );
+       }
 
-               return $text;
+       /**
+        * @deprecated use $this->mStripState->unstripBoth()
+        */
+       function unstripForHTML( $text ) {
+               return $this->mStripState->unstripBoth( $text );
        }
 
        /**
@@ -618,10 +664,7 @@ class Parser
         */
        function insertStripItem( $text, &$state ) {
                $rnd = $this->mUniqPrefix . '-item' . Parser::getRandomString();
-               if ( !$state ) {
-                       $state = array();
-               }
-               $state['item'][$rnd] = $text;
+               $state->general->setPair( $rnd, $text );
                return $rnd;
        }
 
@@ -738,135 +781,191 @@ class Parser
         *
         * @private
         */
-       function doTableStuff ( $t ) {
+       function doTableStuff ( $text ) {
                $fname = 'Parser::doTableStuff';
                wfProfileIn( $fname );
 
-               $t = explode ( "\n" , $t ) ;
-               $td = array () ; # Is currently a td tag open?
-               $ltd = array () ; # Was it TD or TH?
-               $tr = array () ; # Is currently a tr tag open?
-               $ltr = array () ; # tr attributes
-               $has_opened_tr = array(); # Did this table open a <tr> element?
-               $indent_level = 0; # indent level of the table
-               foreach ( $t AS $k => $x )
+               $lines = explode ( "\n" , $text );
+               $td_history = array (); // Is currently a td tag open?
+               $last_tag_history = array (); // Save history of last lag activated (td, th or caption)
+               $tr_history = array (); // Is currently a tr tag open?
+               $tr_attributes = array (); // history of tr attributes
+               $has_opened_tr = array(); // Did this table open a <tr> element?
+               $indent_level = 0; // indent level of the table
+               foreach ( $lines as $key => $line )
                {
-                       $x = trim ( $x ) ;
-                       $fc = substr ( $x , 0 , 1 ) ;
-                       if ( preg_match( '/^(:*)\{\|(.*)$/', $x, $matches ) ) {
+                       $line = trim ( $line );
+
+                       if( $line == '' ) { // empty line, go to next line
+                               continue;
+                       }
+                       $first_character = $line{0};
+                       $matches = array();
+
+                       if ( preg_match( '/^(:*)\{\|(.*)$/' , $line , $matches ) ) {
+                               // First check if we are starting a new table
                                $indent_level = strlen( $matches[1] );
 
-                               $attributes = $this->unstripForHTML( $matches[2] );
+                               $attributes = $this->mStripState->unstripBoth( $matches[2] );
+                               $attributes = Sanitizer::fixTagAttributes ( $attributes , 'table' );
+
+                               $lines[$key] = str_repeat( '<dl><dd>' , $indent_level ) . "<table{$attributes}>";
+                               array_push ( $td_history , false );
+                               array_push ( $last_tag_history , '' );
+                               array_push ( $tr_history , false );
+                               array_push ( $tr_attributes , '' );
+                               array_push ( $has_opened_tr , false );
+                       } else if ( count ( $td_history ) == 0 ) {
+                               // Don't do any of the following
+                               continue;
+                       } else if ( substr ( $line , 0 , 2 ) == '|}' ) { 
+                               // We are ending a table
+                               $line = '</table>' . substr ( $line , 2 );
+                               $last_tag = array_pop ( $last_tag_history );
 
-                               $t[$k] = str_repeat( '<dl><dd>', $indent_level ) .
-                                       '<table' . Sanitizer::fixTagAttributes ( $attributes, 'table' ) . '>' ;
-                               array_push ( $td , false ) ;
-                               array_push ( $ltd , '' ) ;
-                               array_push ( $tr , false ) ;
-                               array_push ( $ltr , '' ) ;
-                               array_push ( $has_opened_tr, false );
-                       }
-                       else if ( count ( $td ) == 0 ) { } # Don't do any of the following
-                       else if ( '|}' == substr ( $x , 0 , 2 ) ) {
-                               $z = "</table>" . substr ( $x , 2);
-                               $l = array_pop ( $ltd ) ;
-                               if ( !array_pop ( $has_opened_tr ) ) $z = "<tr><td></td></tr>" . $z ;
-                               if ( array_pop ( $tr ) ) $z = '</tr>' . $z ;
-                               if ( array_pop ( $td ) ) $z = '</'.$l.'>' . $z ;
-                               array_pop ( $ltr ) ;
-                               $t[$k] = $z . str_repeat( '</dd></dl>', $indent_level );
-                       }
-                       else if ( '|-' == substr ( $x , 0 , 2 ) ) { # Allows for |---------------
-                               $x = substr ( $x , 1 ) ;
-                               while ( $x != '' && substr ( $x , 0 , 1 ) == '-' ) $x = substr ( $x , 1 ) ;
-                               $z = '' ;
-                               $l = array_pop ( $ltd ) ;
+                               if ( !array_pop ( $has_opened_tr ) ) {
+                                       $line = "<tr><td></td></tr>{$line}";
+                               }
+
+                               if ( array_pop ( $tr_history ) ) {
+                                       $line = "</tr>{$line}";
+                               }
+
+                               if ( array_pop ( $td_history ) ) {
+                                       $line = "</{$last_tag}>{$line}";
+                               }
+                               array_pop ( $tr_attributes );
+                               $lines[$key] = $line . str_repeat( '</dd></dl>' , $indent_level );
+                       } else if ( substr ( $line , 0 , 2 ) == '|-' ) {
+                               // Now we have a table row
+                               $line = preg_replace( '#^\|-+#', '', $line );
+
+                               // Whats after the tag is now only attributes
+                               $attributes = $this->mStripState->unstripBoth( $line );
+                               $attributes = Sanitizer::fixTagAttributes ( $attributes , 'tr' );
+                               array_pop ( $tr_attributes );
+                               array_push ( $tr_attributes , $attributes );
+
+                               $line = '';
+                               $last_tag = array_pop ( $last_tag_history );
                                array_pop ( $has_opened_tr );
-                               array_push ( $has_opened_tr , true ) ;
-                               if ( array_pop ( $tr ) ) $z = '</tr>' . $z ;
-                               if ( array_pop ( $td ) ) $z = '</'.$l.'>' . $z ;
-                               array_pop ( $ltr ) ;
-                               $t[$k] = $z ;
-                               array_push ( $tr , false ) ;
-                               array_push ( $td , false ) ;
-                               array_push ( $ltd , '' ) ;
-                               $attributes = $this->unstripForHTML( $x );
-                               array_push ( $ltr , Sanitizer::fixTagAttributes ( $attributes, 'tr' ) ) ;
+                               array_push ( $has_opened_tr , true );
+
+                               if ( array_pop ( $tr_history ) ) {
+                                       $line = '</tr>';
+                               }
+
+                               if ( array_pop ( $td_history ) ) {
+                                       $line = "</{$last_tag}>{$line}";
+                               }
+
+                               $lines[$key] = $line;
+                               array_push ( $tr_history , false );
+                               array_push ( $td_history , false );
+                               array_push ( $last_tag_history , '' );
                        }
-                       else if ( '|' == $fc || '!' == $fc || '|+' == substr ( $x , 0 , 2 ) ) { # Caption
-                               # $x is a table row
-                               if ( '|+' == substr ( $x , 0 , 2 ) ) {
-                                       $fc = '+' ;
-                                       $x = substr ( $x , 1 ) ;
+                       else if ( $first_character == '|' || $first_character == '!' || substr ( $line , 0 , 2 )  == '|+' ) { 
+                               // This might be cell elements, td, th or captions
+                               if ( substr ( $line , 0 , 2 ) == '|+' ) {
+                                       $first_character = '+';
+                                       $line = substr ( $line , 1 );
+                               }
+
+                               $line = substr ( $line , 1 );
+
+                               if ( $first_character == '!' ) {
+                                       $line = str_replace ( '!!' , '||' , $line );
                                }
-                               $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
+                               // 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 );
+                               $cells = StringUtils::explodeMarkup( '||' , $line );
 
-                               $t[$k] = '' ;
+                               $lines[$key] = '';
 
-                               # Loop through each table cell
-                               foreach ( $after AS $theline )
+                               // Loop through each table cell
+                               foreach ( $cells as $cell )
                                {
-                                       $z = '' ;
-                                       if ( $fc != '+' )
+                                       $previous = '';
+                                       if ( $first_character != '+' )
                                        {
-                                               $tra = array_pop ( $ltr ) ;
-                                               if ( !array_pop ( $tr ) ) $z = '<tr'.$tra.">\n" ;
-                                               array_push ( $tr , true ) ;
-                                               array_push ( $ltr , '' ) ;
+                                               $tr_after = array_pop ( $tr_attributes );
+                                               if ( !array_pop ( $tr_history ) ) {
+                                                       $previous = "<tr{$tr_after}>\n";
+                                               }
+                                               array_push ( $tr_history , true );
+                                               array_push ( $tr_attributes , '' );
                                                array_pop ( $has_opened_tr );
-                                               array_push ( $has_opened_tr , true ) ;
+                                               array_push ( $has_opened_tr , true );
+                                       }
+
+                                       $last_tag = array_pop ( $last_tag_history );
+
+                                       if ( array_pop ( $td_history ) ) {
+                                               $previous = "</{$last_tag}>{$previous}";
                                        }
 
-                                       $l = array_pop ( $ltd ) ;
-                                       if ( array_pop ( $td ) ) $z = '</'.$l.'>' . $z ;
-                                       if ( $fc == '|' ) $l = 'td' ;
-                                       else if ( $fc == '!' ) $l = 'th' ;
-                                       else if ( $fc == '+' ) $l = 'caption' ;
-                                       else $l = '' ;
-                                       array_push ( $ltd , $l ) ;
-
-                                       # Cell parameters
-                                       $y = explode ( '|' , $theline , 2 ) ;
-                                       # Note that a '|' inside an invalid link should not
-                                       # be mistaken as delimiting cell parameters
-                                       if ( strpos( $y[0], '[[' ) !== false ) {
-                                               $y = array ($theline);
+                                       if ( $first_character == '|' ) {
+                                               $last_tag = 'td';
+                                       } else if ( $first_character == '!' ) {
+                                               $last_tag = 'th';
+                                       } else if ( $first_character == '+' ) {
+                                               $last_tag = 'caption';
+                                       } else {
+                                               $last_tag = '';
                                        }
-                                       if ( count ( $y ) == 1 )
-                                               $y = "{$z}<{$l}>{$y[0]}" ;
+
+                                       array_push ( $last_tag_history , $last_tag );
+
+                                       // A cell could contain both parameters and data
+                                       $cell_data = explode ( '|' , $cell , 2 );
+
+                                       // Bug 553: Note that a '|' inside an invalid link should not
+                                       // be mistaken as delimiting cell parameters
+                                       if ( strpos( $cell_data[0], '[[' ) !== false ) {
+                                               $cell = "{$previous}<{$last_tag}>{$cell}";
+                                       } else if ( count ( $cell_data ) == 1 )
+                                               $cell = "{$previous}<{$last_tag}>{$cell_data[0]}";
                                        else {
-                                               $attributes = $this->unstripForHTML( $y[0] );
-                                               $y = "{$z}<{$l}".Sanitizer::fixTagAttributes($attributes, $l).">{$y[1]}" ;
+                                               $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
+                                               $attributes = Sanitizer::fixTagAttributes( $attributes , $last_tag );
+                                               $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
                                        }
-                                       $t[$k] .= $y ;
-                                       array_push ( $td , true ) ;
+
+                                       $lines[$key] .= $cell;
+                                       array_push ( $td_history , true );
                                }
                        }
                }
 
-               # Closing open td, tr && table
-               while ( count ( $td ) > 0 )
+               // Closing open td, tr && table
+               while ( count ( $td_history ) > 0 )
                {
-                       $l = array_pop ( $ltd ) ;
-                       if ( array_pop ( $td ) ) $t[] = '</td>' ;
-                       if ( array_pop ( $tr ) ) $t[] = '</tr>' ;
-                       if ( !array_pop ( $has_opened_tr ) ) $t[] = "<tr><td></td></tr>" ;
-                       $t[] = '</table>' ;
+                       if ( array_pop ( $td_history ) ) {
+                               $lines[] = '</td>' ;
+                       }
+                       if ( array_pop ( $tr_history ) ) {
+                               $lines[] = '</tr>' ;
+                       }
+                       if ( !array_pop ( $has_opened_tr ) ) {
+                               $lines[] = "<tr><td></td></tr>" ;
+                       }
+
+                       $lines[] = '</table>' ;
+               }
+
+               $output = implode ( "\n" , $lines ) ;
+
+               // special case: don't return empty table
+               if( $output == "<table>\n<tr><td></td></tr>\n</table>" ) {
+                       $output = '';
                }
 
-               $t = implode ( "\n" , $t ) ;
-               # special case: don't return empty table
-               if($t == "<table>\n<tr><td></td></tr>\n</table>")
-                       $t = '';
                wfProfileOut( $fname );
-               return $t ;
+
+               return $output;
        }
 
        /**
@@ -881,10 +980,16 @@ class Parser
                $fname = 'Parser::internalParse';
                wfProfileIn( $fname );
 
+               # Hook to suspend the parser in this state
+               if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$this->mStripState ) ) ) {
+                       wfProfileOut( $fname );
+                       return $text ;
+               }
+
                # Remove <noinclude> tags and <includeonly> sections
                $text = strtr( $text, array( '<onlyinclude>' => '' , '</onlyinclude>' => '' ) );
                $text = strtr( $text, array( '<noinclude>' => '', '</noinclude>' => '') );
-               $text = preg_replace( '/<includeonly>.*?<\/includeonly>/s', '', $text );
+               $text = StringUtils::delimiterReplace( '<includeonly>', '</includeonly>', '', $text );
 
                $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ) );
 
@@ -927,9 +1032,52 @@ class Parser
         * @private
         */
        function &doMagicLinks( &$text ) {
-               $text = $this->magicISBN( $text );
-               $text = $this->magicRFC( $text, 'RFC ', 'rfcurl' );
-               $text = $this->magicRFC( $text, 'PMID ', 'pubmedurl' );
+               wfProfileIn( __METHOD__ );
+               $text = preg_replace_callback(
+                       '!(?:                           # Start cases
+                           <a.*?</a> |                 # Skip link text
+                           <.*?> |                     # Skip stuff inside HTML elements
+                           (?:RFC|PMID)\s+([0-9]+) |   # RFC or PMID, capture number as m[1]
+                           ISBN\s+(\b[0-9Xx\ \-]+)     # ISBN, capture number as m[2]
+                       )!x', array( &$this, 'magicLinkCallback' ), $text );
+               wfProfileOut( __METHOD__ );
+               return $text;
+       }
+
+       function magicLinkCallback( $m ) {
+               if ( substr( $m[0], 0, 1 ) == '<' ) {
+                       # Skip HTML element
+                       return $m[0];
+               } elseif ( substr( $m[0], 0, 4 ) == 'ISBN' ) {
+                       $isbn = $m[2];
+                       $num = strtr( $isbn, array(
+                               '-' => '',
+                               ' ' => '',
+                               'x' => 'X',
+                       ));
+                       $titleObj = SpecialPage::getTitleFor( 'Booksources' );
+                       $text = '<a href="' .
+                               $titleObj->escapeLocalUrl( "isbn=$num" ) .
+                               "\" class=\"internal\">ISBN $isbn</a>";
+               } else {
+                       if ( substr( $m[0], 0, 3 ) == 'RFC' ) {
+                               $keyword = 'RFC';
+                               $urlmsg = 'rfcurl';
+                               $id = $m[1];
+                       } elseif ( substr( $m[0], 0, 4 ) == 'PMID' ) {
+                               $keyword = 'PMID';
+                               $urlmsg = 'pubmedurl';
+                               $id = $m[1];
+                       } else {
+                               throw new MWException( __METHOD__.': unrecognised match type "' .
+                                       substr($m[0], 0, 20 ) . '"' );
+                       }
+
+                       $url = wfMsg( $urlmsg, $id);
+                       $sk =& $this->mOptions->getSkin();
+                       $la = $sk->getExternalLinkAttributes( $url, $keyword.$id );
+                       $text = "<a href=\"{$url}\"{$la}>{$keyword} {$id}</a>";
+               }
                return $text;
        }
 
@@ -1004,9 +1152,9 @@ class Parser
                                        }
                                        # Count the number of occurrences of bold and italics mark-ups.
                                        # We are not counting sequences of five apostrophes.
-                                       if ( strlen( $arr[$i] ) == 2 ) $numitalics++;  else
-                                       if ( strlen( $arr[$i] ) == 3 ) $numbold++;     else
-                                       if ( strlen( $arr[$i] ) == 5 ) { $numitalics++; $numbold++; }
+                                       if ( strlen( $arr[$i] ) == 2 )      { $numitalics++;             }
+                                       else if ( strlen( $arr[$i] ) == 3 ) { $numbold++;                }
+                                       else if ( strlen( $arr[$i] ) == 5 ) { $numitalics++; $numbold++; }
                                }
                                $i++;
                        }
@@ -1162,6 +1310,7 @@ class Parser
                        # The characters '<' and '>' (which were escaped by
                        # removeHTMLtags()) should not be included in
                        # URLs, per RFC 2396.
+                       $m2 = array();
                        if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE)) {
                                $text = substr($url, $m2[0][1]) . ' ' . $text;
                                $url = substr($url, 0, $m2[0][1]);
@@ -1197,7 +1346,7 @@ class Parser
                        }
 
                        $text = $wgContLang->markNoConversion($text);
-                       
+
                        $url = Sanitizer::cleanUrl( $url );
 
                        # Process the trail (i.e. everything after this link up until start of the next link),
@@ -1240,6 +1389,7 @@ class Parser
                        $protocol = $bits[$i++];
                        $remainder = $bits[$i++];
 
+                       $m = array();
                        if ( preg_match( '/^('.EXT_LINK_URL_CLASS.'+)(.*)$/s', $remainder, $m ) ) {
                                # Found some characters after the protocol that look promising
                                $url = $protocol . $m[1];
@@ -1247,10 +1397,10 @@ class Parser
 
                                # special case: handle urls as url args:
                                # http://www.example.com/foo?=http://www.example.com/bar
-                               if(strlen($trail) == 0 && 
+                               if(strlen($trail) == 0 &&
                                        isset($bits[$i]) &&
                                        preg_match('/^'. wfUrlProtocols() . '$/S', $bits[$i]) &&
-                                       preg_match( '/^('.EXT_LINK_URL_CLASS.'+)(.*)$/s', $bits[$i + 1], $m )) 
+                                       preg_match( '/^('.EXT_LINK_URL_CLASS.'+)(.*)$/s', $bits[$i + 1], $m ))
                                {
                                        # add protocol, arg
                                        $url .= $bits[$i] . $m[1]; # protocol, url as arg to previous link
@@ -1261,6 +1411,7 @@ class Parser
                                # The characters '<' and '>' (which were escaped by
                                # removeHTMLtags()) should not be included in
                                # URLs, per RFC 2396.
+                               $m2 = array();
                                if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE)) {
                                        $trail = substr($url, $m2[0][1]) . $trail;
                                        $url = substr($url, 0, $m2[0][1]);
@@ -1391,11 +1542,12 @@ class Parser
                $useLinkPrefixExtension = $wgContLang->linkPrefixExtension();
 
                if( is_null( $this->mTitle ) ) {
-                       throw new MWException( 'nooo' );
+                       throw new MWException( __METHOD__.": \$this->mTitle is null\n" );
                }
                $nottalk = !$this->mTitle->isTalkPage();
 
                if ( $useLinkPrefixExtension ) {
+                       $m = array();
                        if ( preg_match( $e2, $s, $m ) ) {
                                $first_prefix = $m[2];
                        } else {
@@ -1406,10 +1558,8 @@ class Parser
                }
 
                $selflink = $this->mTitle->getPrefixedText();
-               wfProfileOut( $fname.'-setup' );
-
-               $checkVariantLink = sizeof($wgContLang->getVariants())>1;
                $useSubpages = $this->areSubpagesAllowed();
+               wfProfileOut( $fname.'-setup' );
 
                # Loop for each link
                for ($k = 0; isset( $a[$k] ); $k++) {
@@ -1432,6 +1582,7 @@ class Parser
 
                        $might_be_img = false;
 
+                       wfProfileIn( "$fname-e1" );
                        if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
                                $text = $m[2];
                                # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
@@ -1442,28 +1593,34 @@ class Parser
                                # Still some problems for cases where the ] is meant to be outside punctuation,
                                # and no image is in sight. See bug 2095.
                                #
-                               if( $text !== '' && 
-                                       preg_match( "/^\](.*)/s", $m[3], $n ) && 
-                                       strpos($text, '[') !== false 
-                               ) 
+                               if( $text !== '' &&
+                                       substr( $m[3], 0, 1 ) === ']' &&
+                                       strpos($text, '[') !== false
+                               )
                                {
                                        $text .= ']'; # so that replaceExternalLinks($text) works later
-                                       $m[3] = $n[1];
+                                       $m[3] = substr( $m[3], 1 );
                                }
                                # fix up urlencoded title texts
-                               if(preg_match('/%/', $m[1] ))
+                               if( strpos( $m[1], '%' ) !== false ) {
                                        # Should anchors '#' also be rejected?
                                        $m[1] = str_replace( array('<', '>'), array('&lt;', '&gt;'), urldecode($m[1]) );
+                               }
                                $trail = $m[3];
                        } elseif( preg_match($e1_img, $line, $m) ) { # Invalid, but might be an image with a link in its caption
                                $might_be_img = true;
                                $text = $m[2];
-                               if(preg_match('/%/', $m[1] )) $m[1] = urldecode($m[1]);
+                               if ( strpos( $m[1], '%' ) !== false ) {
+                                      $m[1] = urldecode($m[1]);
+                               }
                                $trail = "";
                        } else { # Invalid form; output directly
                                $s .= $prefix . '[[' . $line ;
+                               wfProfileOut( "$fname-e1" );
                                continue;
                        }
+                       wfProfileOut( "$fname-e1" );
+                       wfProfileIn( "$fname-misc" );
 
                        # Don't allow internal links to pages containing
                        # PROTO: where PROTO is a valid URL protocol; these
@@ -1486,38 +1643,37 @@ class Parser
                                $link = substr($link, 1);
                        }
 
-                       $nt = Title::newFromText( $this->unstripNoWiki($link, $this->mStripState) );
+                       wfProfileOut( "$fname-misc" );
+                       wfProfileIn( "$fname-title" );
+                       $nt = Title::newFromText( $this->mStripState->unstripNoWiki($link) );
                        if( !$nt ) {
                                $s .= $prefix . '[[' . $line;
+                               wfProfileOut( "$fname-title" );
                                continue;
                        }
 
-                       #check other language variants of the link
-                       #if the article does not exist
-                       if( $checkVariantLink
-                           && $nt->getArticleID() == 0 ) {
-                               $wgContLang->findVariantLink($link, $nt);
-                       }
-
                        $ns = $nt->getNamespace();
                        $iw = $nt->getInterWiki();
+                       wfProfileOut( "$fname-title" );
 
                        if ($might_be_img) { # if this is actually an invalid link
+                               wfProfileIn( "$fname-might_be_img" );
                                if ($ns == NS_IMAGE && $noforce) { #but might be an image
                                        $found = false;
                                        while (isset ($a[$k+1]) ) {
                                                #look at the next 'line' to see if we can close it there
                                                $spliced = array_splice( $a, $k + 1, 1 );
                                                $next_line = array_shift( $spliced );
-                                               if( preg_match("/^(.*?]].*?)]](.*)$/sD", $next_line, $m) ) {
-                                               # the first ]] closes the inner link, the second the image
+                                               $m = explode( ']]', $next_line, 3 );
+                                               if ( count( $m ) == 3 ) {
+                                                       # the first ]] closes the inner link, the second the image
                                                        $found = true;
-                                                       $text .= '[[' . $m[1];
+                                                       $text .= "[[{$m[0]}]]{$m[1]}";
                                                        $trail = $m[2];
                                                        break;
-                                               } elseif( preg_match("/^.*?]].*$/sD", $next_line, $m) ) {
+                                               } elseif ( count( $m ) == 2 ) {
                                                        #if there's exactly one ]] that's fine, we'll keep looking
-                                                       $text .= '[[' . $m[0];
+                                                       $text .= "[[{$m[0]}]]{$m[1]}";
                                                } else {
                                                        #if $next_line is invalid too, we need look no further
                                                        $text .= '[[' . $next_line;
@@ -1528,35 +1684,40 @@ class Parser
                                                # we couldn't find the end of this imageLink, so output it raw
                                                #but don't ignore what might be perfectly normal links in the text we've examined
                                                $text = $this->replaceInternalLinks($text);
-                                               $s .= $prefix . '[[' . $link . '|' . $text;
+                                               $s .= "{$prefix}[[$link|$text";
                                                # note: no $trail, because without an end, there *is* no trail
+                                               wfProfileOut( "$fname-might_be_img" );
                                                continue;
                                        }
                                } else { #it's not an image, so output it raw
-                                       $s .= $prefix . '[[' . $link . '|' . $text;
+                                       $s .= "{$prefix}[[$link|$text";
                                        # note: no $trail, because without an end, there *is* no trail
+                                       wfProfileOut( "$fname-might_be_img" );
                                        continue;
                                }
+                               wfProfileOut( "$fname-might_be_img" );
                        }
 
                        $wasblank = ( '' == $text );
                        if( $wasblank ) $text = $link;
 
-
                        # Link not escaped by : , create the various objects
                        if( $noforce ) {
 
                                # Interwikis
+                               wfProfileIn( "$fname-interwiki" );
                                if( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgContLang->getLanguageName( $iw ) ) {
                                        $this->mOutput->addLanguageLink( $nt->getFullText() );
                                        $s = rtrim($s . "\n");
                                        $s .= trim($prefix . $trail, "\n") == '' ? '': $prefix . $trail;
+                                       wfProfileOut( "$fname-interwiki" );
                                        continue;
                                }
+                               wfProfileOut( "$fname-interwiki" );
 
                                if ( $ns == NS_IMAGE ) {
                                        wfProfileIn( "$fname-image" );
-                                       if ( !wfIsBadImage( $nt->getDBkey() ) ) {
+                                       if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
                                                # recursively parse links inside the image caption
                                                # actually, this will parse them in any other parameters, too,
                                                # but it might be hard to fix that, and it doesn't matter ATM
@@ -1630,6 +1791,7 @@ class Parser
                                        // upload on the shared repository, and we want to see its
                                        // auto-generated page.
                                        $s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix );
+                                       $this->mOutput->addLink( $nt );
                                        continue;
                                }
                        }
@@ -1642,11 +1804,12 @@ class Parser
        /**
         * Make a link placeholder. The text returned can be later resolved to a real link with
         * replaceLinkHolders(). This is done for two reasons: firstly to avoid further
-        * parsing of interwiki links, and secondly to allow all extistence checks and
+        * parsing of interwiki links, and secondly to allow all existence checks and
         * article length checks (for stub links) to be bundled into a single query.
         *
         */
        function makeLinkHolder( &$nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
+               wfProfileIn( __METHOD__ );
                if ( ! is_object($nt) ) {
                        # Fail gracefully
                        $retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}";
@@ -1668,6 +1831,7 @@ class Parser
                                $retVal = '<!--LINK '. ($nr-1) ."-->{$trail}";
                        }
                }
+               wfProfileOut( __METHOD__ );
                return $retVal;
        }
 
@@ -1705,7 +1869,7 @@ class Parser
         * @return string less-or-more HTML with NOPARSE bits
         */
        function armorLinks( $text ) {
-               return preg_replace( "/\b(" . wfUrlProtocols() . ')/',
+               return preg_replace( '/\b(' . wfUrlProtocols() . ')/',
                        "{$this->mUniqPrefix}NOPARSE$1", $text );
        }
 
@@ -1739,6 +1903,9 @@ class Parser
                wfProfileIn( $fname );
                $ret = $target; # default return value is no change
 
+               # bug 7425
+               $target = trim( $target );
+
                # Some namespaces don't allow subpages,
                # so only perform processing if subpages are allowed
                if( $this->areSubpagesAllowed() ) {
@@ -2209,13 +2376,34 @@ class Parser
                 * expensive to check many times.
                 */
                static $varCache = array();
-               if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$varCache ) ) )
-                       if ( isset( $varCache[$index] ) )
+               if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$varCache ) ) ) {
+                       if ( isset( $varCache[$index] ) ) {
                                return $varCache[$index];
+                       }
+               }
 
                $ts = time();
                wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) );
 
+               # Use the time zone
+               global $wgLocaltimezone;
+               if ( isset( $wgLocaltimezone ) ) {
+                       $oldtz = getenv( 'TZ' );
+                       putenv( 'TZ='.$wgLocaltimezone );
+               }
+               $localTimestamp = date( 'YmdHis', $ts );
+               $localMonth = date( 'm', $ts );
+               $localMonthName = date( 'n', $ts );
+               $localDay = date( 'j', $ts );
+               $localDay2 = date( 'd', $ts );
+               $localDayOfWeek = date( 'w', $ts );
+               $localWeek = date( 'W', $ts );
+               $localYear = date( 'Y', $ts );
+               $localHour = date( 'H', $ts );
+               if ( isset( $wgLocaltimezone ) ) {
+                       putenv( 'TZ='.$oldtz );
+               }
+
                switch ( $index ) {
                        case 'currentmonth':
                                return $varCache[$index] = $wgContLang->formatNum( date( 'm', $ts ) );
@@ -2229,6 +2417,18 @@ class Parser
                                return $varCache[$index] = $wgContLang->formatNum( date( 'j', $ts ) );
                        case 'currentday2':
                                return $varCache[$index] = $wgContLang->formatNum( date( 'd', $ts ) );
+                       case 'localmonth':
+                               return $varCache[$index] = $wgContLang->formatNum( $localMonth );
+                       case 'localmonthname':
+                               return $varCache[$index] = $wgContLang->getMonthName( $localMonthName );
+                       case 'localmonthnamegen':
+                               return $varCache[$index] = $wgContLang->getMonthNameGen( $localMonthName );
+                       case 'localmonthabbrev':
+                               return $varCache[$index] = $wgContLang->getMonthAbbreviation( $localMonthName );
+                       case 'localday':
+                               return $varCache[$index] = $wgContLang->formatNum( $localDay );
+                       case 'localday2':
+                               return $varCache[$index] = $wgContLang->formatNum( $localDay2 );
                        case 'pagename':
                                return $this->mTitle->getText();
                        case 'pagenamee':
@@ -2267,6 +2467,16 @@ class Parser
                                return $subjPage->getPrefixedUrl();
                        case 'revisionid':
                                return $this->mRevisionId;
+                       case 'revisionday':
+                               return intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
+                       case 'revisionday2':
+                               return substr( $this->getRevisionTimestamp(), 6, 2 );
+                       case 'revisionmonth':
+                               return intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
+                       case 'revisionyear':
+                               return substr( $this->getRevisionTimestamp(), 0, 4 );
+                       case 'revisiontimestamp':
+                               return $this->getRevisionTimestamp();
                        case 'namespace':
                                return str_replace('_',' ',$wgContLang->getNsText( $this->mTitle->getNamespace() ) );
                        case 'namespacee':
@@ -2285,27 +2495,44 @@ class Parser
                                return $varCache[$index] = $wgContLang->formatNum( date( 'Y', $ts ), true );
                        case 'currenttime':
                                return $varCache[$index] = $wgContLang->time( wfTimestamp( TS_MW, $ts ), false, false );
+                       case 'currenthour':
+                               return $varCache[$index] = $wgContLang->formatNum( date( 'H', $ts ), true );
                        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 'currentdow':
                                return $varCache[$index] = $wgContLang->formatNum( date( 'w', $ts ) );
+                       case 'localdayname':
+                               return $varCache[$index] = $wgContLang->getWeekdayName( $localDayOfWeek + 1 );
+                       case 'localyear':
+                               return $varCache[$index] = $wgContLang->formatNum( $localYear, true );
+                       case 'localtime':
+                               return $varCache[$index] = $wgContLang->time( $localTimestamp, false, false );
+                       case 'localhour':
+                               return $varCache[$index] = $wgContLang->formatNum( $localHour, true );
+                       case 'localweek':
+                               // @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
+                               // int to remove the padding
+                               return $varCache[$index] = $wgContLang->formatNum( (int)$localWeek );
+                       case 'localdow':
+                               return $varCache[$index] = $wgContLang->formatNum( $localDayOfWeek );
                        case 'numberofarticles':
-                               return $varCache[$index] = $wgContLang->formatNum( wfNumberOfArticles() );
+                               return $varCache[$index] = $wgContLang->formatNum( SiteStats::articles() );
                        case 'numberoffiles':
-                               return $varCache[$index] = $wgContLang->formatNum( wfNumberOfFiles() );
+                               return $varCache[$index] = $wgContLang->formatNum( SiteStats::images() );
                        case 'numberofusers':
-                               return $varCache[$index] = $wgContLang->formatNum( wfNumberOfUsers() );
+                               return $varCache[$index] = $wgContLang->formatNum( SiteStats::users() );
                        case 'numberofpages':
-                               return $varCache[$index] = $wgContLang->formatNum( wfNumberOfPages() );
+                               return $varCache[$index] = $wgContLang->formatNum( SiteStats::pages() );
                        case 'numberofadmins':
-                               return $varCache[$index]  = $wgContLang->formatNum( wfNumberOfAdmins() );
+                               return $varCache[$index]  = $wgContLang->formatNum( SiteStats::admins() );
                        case 'currenttimestamp':
                                return $varCache[$index] = wfTimestampNow();
+                       case 'localtimestamp':
+                               return $varCache[$index] = $localTimestamp;
                        case 'currentversion':
-                               global $wgVersion;
-                               return $wgVersion;
+                               return $varCache[$index] = SpecialVersion::getVersion();
                        case 'sitename':
                                return $wgSitename;
                        case 'server':
@@ -2355,172 +2582,169 @@ class Parser
         *     '{' => array(                            # opening parentheses
         *                                      'end' => '}',   # closing parentheses
         *                                      'cb' => array(2 => callback,    # replacement callback to call if {{..}} is found
-        *                                                                4 => callback         # replacement callback to call if {{{{..}}}} is found
+        *                                                                3 => callback         # replacement callback to call if {{{..}}} is found
         *                                                                )
         *                                      )
+        *                                      'min' => 2,     # Minimum parenthesis count in cb
+        *                                      'max' => 3,     # Maximum parenthesis count in cb
         * @private
         */
        function replace_callback ($text, $callbacks) {
-               wfProfileIn( __METHOD__ . '-self' );
+               wfProfileIn( __METHOD__ );
                $openingBraceStack = array();   # this array will hold a stack of parentheses which are not closed yet
                $lastOpeningBrace = -1;                 # last not closed parentheses
 
-               for ($i = 0; $i < strlen($text); $i++) {
-                       # check for any opening brace
-                       $rule = null;
-                       $nextPos = -1;
-                       foreach ($callbacks as $key => $value) {
-                               $pos = strpos ($text, $key, $i);
-                               if (false !== $pos && (-1 == $nextPos || $pos < $nextPos)) {
-                                       $rule = $value;
-                                       $nextPos = $pos;
-                               }
-                       }
-
-                       if ($lastOpeningBrace >= 0) {
-                               $pos = strpos ($text, $openingBraceStack[$lastOpeningBrace]['braceEnd'], $i);
+               $validOpeningBraces = implode( '', array_keys( $callbacks ) );
 
-                               if (false !== $pos && (-1 == $nextPos || $pos < $nextPos)){
-                                       $rule = null;
-                                       $nextPos = $pos;
-                               }
-
-                               $pos = strpos ($text, '|', $i);
-
-                               if (false !== $pos && (-1 == $nextPos || $pos < $nextPos)){
-                                       $rule = null;
-                                       $nextPos = $pos;
-                               }
+               $i = 0;
+               while ( $i < strlen( $text ) ) {
+                       # Find next opening brace, closing brace or pipe
+                       if ( $lastOpeningBrace == -1 ) {
+                               $currentClosing = '';
+                               $search = $validOpeningBraces;
+                       } else {
+                               $currentClosing = $openingBraceStack[$lastOpeningBrace]['braceEnd'];
+                               $search = $validOpeningBraces . '|' . $currentClosing;
                        }
-
-                       if ($nextPos == -1)
+                       $rule = null;
+                       $i += strcspn( $text, $search, $i );
+                       if ( $i < strlen( $text ) ) {
+                               if ( $text[$i] == '|' ) {
+                                       $found = 'pipe';
+                               } elseif ( $text[$i] == $currentClosing ) {
+                                       $found = 'close';
+                               } elseif ( isset( $callbacks[$text[$i]] ) ) {
+                                       $found = 'open';
+                                       $rule = $callbacks[$text[$i]];
+                               } else {
+                                       # Some versions of PHP have a strcspn which stops on null characters
+                                       # Ignore and continue
+                                       ++$i;
+                                       continue;
+                               }
+                       } else {
+                               # All done
                                break;
+                       }
 
-                       $i = $nextPos;
-
-                       # found openning brace, lets add it to parentheses stack
-                       if (null != $rule) {
+                       if ( $found == 'open' ) {
+                               # found opening brace, let's add it to parentheses stack
                                $piece = array('brace' => $text[$i],
                                                           'braceEnd' => $rule['end'],
-                                                          'count' => 1,
                                                           'title' => '',
                                                           'parts' => null);
 
-                               # count openning brace characters
-                               while ($i+1 < strlen($text) && $text[$i+1] == $piece['brace']) {
-                                       $piece['count']++;
-                                       $i++;
-                               }
-
-                               $piece['startAt'] = $i+1;
-                               $piece['partStart'] = $i+1;
+                               # count opening brace characters
+                               $piece['count'] = strspn( $text, $piece['brace'], $i );
+                               $piece['startAt'] = $piece['partStart'] = $i + $piece['count'];
+                               $i += $piece['count'];
 
-                               # we need to add to stack only if openning brace count is enough for any given rule
-                               foreach ($rule['cb'] as $cnt => $fn) {
-                                       if ($piece['count'] >= $cnt) {
-                                               $lastOpeningBrace ++;
-                                               $openingBraceStack[$lastOpeningBrace] = $piece;
-                                               break;
+                               # we need to add to stack only if opening brace count is enough for one of the rules
+                               if ( $piece['count'] >= $rule['min'] ) {
+                                       $lastOpeningBrace ++;
+                                       $openingBraceStack[$lastOpeningBrace] = $piece;
+                               }
+                       } elseif ( $found == 'close' ) {
+                               # lets check if it is enough characters for closing brace
+                               $maxCount = $openingBraceStack[$lastOpeningBrace]['count'];
+                               $count = strspn( $text, $text[$i], $i, $maxCount );
+
+                               # check for maximum matching characters (if there are 5 closing
+                               # characters, we will probably need only 3 - depending on the rules)
+                               $matchingCount = 0;
+                               $matchingCallback = null;
+                               $cbType = $callbacks[$openingBraceStack[$lastOpeningBrace]['brace']];
+                               if ( $count > $cbType['max'] ) {
+                                       # The specified maximum exists in the callback array, unless the caller
+                                       # has made an error
+                                       $matchingCount = $cbType['max'];
+                               } else {
+                                       # Count is less than the maximum
+                                       # Skip any gaps in the callback array to find the true largest match
+                                       # Need to use array_key_exists not isset because the callback can be null
+                                       $matchingCount = $count;
+                                       while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $cbType['cb'] ) ) {
+                                               --$matchingCount;
                                        }
                                }
 
-                               continue;
-                       }
-                       else if ($lastOpeningBrace >= 0) {
-                               # first check if it is a closing brace
-                               if ($openingBraceStack[$lastOpeningBrace]['braceEnd'] == $text[$i]) {
-                                       # lets check if it is enough characters for closing brace
-                                       $count = 1;
-                                       while ($i+$count < strlen($text) && $text[$i+$count] == $text[$i])
-                                               $count++;
-
-                                       # if there are more closing parentheses than opening ones, we parse less
-                                       if ($openingBraceStack[$lastOpeningBrace]['count'] < $count)
-                                               $count = $openingBraceStack[$lastOpeningBrace]['count'];
-
-                                       # check for maximum matching characters (if there are 5 closing characters, we will probably need only 3 - depending on the rules)
-                                       $matchingCount = 0;
-                                       $matchingCallback = null;
-                                       foreach ($callbacks[$openingBraceStack[$lastOpeningBrace]['brace']]['cb'] as $cnt => $fn) {
-                                               if ($count >= $cnt && $matchingCount < $cnt) {
-                                                       $matchingCount = $cnt;
-                                                       $matchingCallback = $fn;
-                                               }
-                                       }
+                               if ($matchingCount <= 0) {
+                                       $i += $count;
+                                       continue;
+                               }
+                               $matchingCallback = $cbType['cb'][$matchingCount];
 
-                                       if ($matchingCount == 0) {
-                                               $i += $count - 1;
-                                               continue;
-                                       }
+                               # let's set a title or last part (if '|' was found)
+                               if (null === $openingBraceStack[$lastOpeningBrace]['parts']) {
+                                       $openingBraceStack[$lastOpeningBrace]['title'] =
+                                               substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
+                                               $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
+                               } else {
+                                       $openingBraceStack[$lastOpeningBrace]['parts'][] =
+                                               substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
+                                               $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
+                               }
 
-                                       # lets set a title or last part (if '|' was found)
-                                       if (null === $openingBraceStack[$lastOpeningBrace]['parts'])
-                                               $openingBraceStack[$lastOpeningBrace]['title'] = substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
-                                       else
-                                               $openingBraceStack[$lastOpeningBrace]['parts'][] = substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
-
-                                       $pieceStart = $openingBraceStack[$lastOpeningBrace]['startAt'] - $matchingCount;
-                                       $pieceEnd = $i + $matchingCount;
-
-                                       if( is_callable( $matchingCallback ) ) {
-                                               $cbArgs = array (
-                                                                                'text' => substr($text, $pieceStart, $pieceEnd - $pieceStart),
-                                                                                'title' => trim($openingBraceStack[$lastOpeningBrace]['title']),
-                                                                                'parts' => $openingBraceStack[$lastOpeningBrace]['parts'],
-                                                                                '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;
-                                       }
-                                       else {
-                                               # null value for callback means that parentheses should be parsed, but not replaced
-                                               $i += $matchingCount - 1;
-                                       }
+                               $pieceStart = $openingBraceStack[$lastOpeningBrace]['startAt'] - $matchingCount;
+                               $pieceEnd = $i + $matchingCount;
+
+                               if( is_callable( $matchingCallback ) ) {
+                                       $cbArgs = array (
+                                                                        'text' => substr($text, $pieceStart, $pieceEnd - $pieceStart),
+                                                                        'title' => trim($openingBraceStack[$lastOpeningBrace]['title']),
+                                                                        'parts' => $openingBraceStack[$lastOpeningBrace]['parts'],
+                                                                        'lineStart' => (($pieceStart > 0) && ($text[$pieceStart-1] == "\n")),
+                                                                        );
+                                       # finally we can call a user callback and replace piece of text
+                                       $replaceWith = call_user_func( $matchingCallback, $cbArgs );
+                                       $text = substr($text, 0, $pieceStart) . $replaceWith . substr($text, $pieceEnd);
+                                       $i = $pieceStart + strlen($replaceWith);
+                               } else {
+                                       # null value for callback means that parentheses should be parsed, but not replaced
+                                       $i += $matchingCount;
+                               }
 
-                                       # reset last openning parentheses, but keep it in case there are unused characters
-                                       $piece = array('brace' => $openingBraceStack[$lastOpeningBrace]['brace'],
-                                                                  'braceEnd' => $openingBraceStack[$lastOpeningBrace]['braceEnd'],
-                                                                  'count' => $openingBraceStack[$lastOpeningBrace]['count'],
-                                                                  'title' => '',
-                                                                  'parts' => null,
-                                                                  'startAt' => $openingBraceStack[$lastOpeningBrace]['startAt']);
-                                       $openingBraceStack[$lastOpeningBrace--] = null;
-
-                                       if ($matchingCount < $piece['count']) {
-                                               $piece['count'] -= $matchingCount;
-                                               $piece['startAt'] -= $matchingCount;
-                                               $piece['partStart'] = $piece['startAt'];
-                                               # do we still qualify for any callback with remaining count?
-                                               foreach ($callbacks[$piece['brace']]['cb'] as $cnt => $fn) {
-                                                       if ($piece['count'] >= $cnt) {
-                                                               $lastOpeningBrace ++;
-                                                               $openingBraceStack[$lastOpeningBrace] = $piece;
-                                                               break;
-                                                       }
+                               # reset last opening parentheses, but keep it in case there are unused characters
+                               $piece = array('brace' => $openingBraceStack[$lastOpeningBrace]['brace'],
+                                                          'braceEnd' => $openingBraceStack[$lastOpeningBrace]['braceEnd'],
+                                                          'count' => $openingBraceStack[$lastOpeningBrace]['count'],
+                                                          'title' => '',
+                                                          'parts' => null,
+                                                          'startAt' => $openingBraceStack[$lastOpeningBrace]['startAt']);
+                               $openingBraceStack[$lastOpeningBrace--] = null;
+
+                               if ($matchingCount < $piece['count']) {
+                                       $piece['count'] -= $matchingCount;
+                                       $piece['startAt'] -= $matchingCount;
+                                       $piece['partStart'] = $piece['startAt'];
+                                       # do we still qualify for any callback with remaining count?
+                                       $currentCbList = $callbacks[$piece['brace']]['cb'];
+                                       while ( $piece['count'] ) {
+                                               if ( array_key_exists( $piece['count'], $currentCbList ) ) {
+                                                       $lastOpeningBrace++;
+                                                       $openingBraceStack[$lastOpeningBrace] = $piece;
+                                                       break;
                                                }
+                                               --$piece['count'];
                                        }
-                                       continue;
                                }
-
+                       } elseif ( $found == 'pipe' ) {
                                # lets set a title if it is a first separator, or next part otherwise
-                               if ($text[$i] == '|') {
-                                       if (null === $openingBraceStack[$lastOpeningBrace]['parts']) {
-                                               $openingBraceStack[$lastOpeningBrace]['title'] = substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
-                                               $openingBraceStack[$lastOpeningBrace]['parts'] = array();
-                                       }
-                                       else
-                                               $openingBraceStack[$lastOpeningBrace]['parts'][] = substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
-
-                                       $openingBraceStack[$lastOpeningBrace]['partStart'] = $i + 1;
+                               if (null === $openingBraceStack[$lastOpeningBrace]['parts']) {
+                                       $openingBraceStack[$lastOpeningBrace]['title'] =
+                                               substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
+                                               $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
+                                       $openingBraceStack[$lastOpeningBrace]['parts'] = array();
+                               } else {
+                                       $openingBraceStack[$lastOpeningBrace]['parts'][] =
+                                               substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
+                                               $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
                                }
+                               $openingBraceStack[$lastOpeningBrace]['partStart'] = ++$i;
                        }
                }
 
-               wfProfileOut( __METHOD__ . '-self' );
+               wfProfileOut( __METHOD__ );
                return $text;
        }
 
@@ -2541,11 +2765,11 @@ class Parser
         */
        function replaceVariables( $text, $args = array(), $argsOnly = false ) {
                # Prevent too big inclusions
-               if( strlen( $text ) > MAX_INCLUDE_SIZE ) {
+               if( strlen( $text ) > $this->mOptions->getMaxIncludeSize() ) {
                        return $text;
                }
 
-               $fname = 'Parser::replaceVariables';
+               $fname = __METHOD__ /*. '-L' . count( $this->mArgStack )*/;
                wfProfileIn( $fname );
 
                # This function is called recursively. To keep track of arguments we need a stack:
@@ -2555,16 +2779,28 @@ class Parser
                if ( !$argsOnly ) {
                        $braceCallbacks[2] = array( &$this, 'braceSubstitution' );
                }
-               if ( $this->mOutputType == OT_HTML || $this->mOutputType == OT_WIKI ) {
+               if ( $this->mOutputType != OT_MSG ) {
                        $braceCallbacks[3] = array( &$this, 'argSubstitution' );
                }
-               $callbacks = array();
-               $callbacks['{'] = array('end' => '}', 'cb' => $braceCallbacks);
-               $callbacks['['] = array('end' => ']', 'cb' => array(2=>null));
-               $text = $this->replace_callback ($text, $callbacks);
-
-               array_pop( $this->mArgStack );
+               if ( $braceCallbacks ) {
+                       $callbacks = array(
+                               '{' => array(
+                                       'end' => '}',
+                                       'cb' => $braceCallbacks,
+                                       'min' => $argsOnly ? 3 : 2,
+                                       'max' => isset( $braceCallbacks[3] ) ? 3 : 2,
+                               ),
+                               '[' => array(
+                                       'end' => ']',
+                                       'cb' => array(2=>null),
+                                       'min' => 2,
+                                       'max' => 2,
+                               )
+                       );
+                       $text = $this->replace_callback ($text, $callbacks);
 
+                       array_pop( $this->mArgStack );
+               }
                wfProfileOut( $fname );
                return $text;
        }
@@ -2574,8 +2810,9 @@ class Parser
         * @private
         */
        function variableSubstitution( $matches ) {
+               global $wgContLang;
                $fname = 'Parser::variableSubstitution';
-               $varname = $matches[1];
+               $varname = $wgContLang->lc($matches[1]);
                wfProfileIn( $fname );
                $skip = false;
                if ( $this->mOutputType == OT_WIKI ) {
@@ -2589,8 +2826,14 @@ class Parser
                }
                if ( !$skip && array_key_exists( $varname, $this->mVariables ) ) {
                        $id = $this->mVariables[$varname];
-                       $text = $this->getVariableValue( $id );
-                       $this->mOutput->mContainsOldMagic = true;
+                       # Now check if we did really match, case sensitive or not
+                       $mw =& MagicWord::get( $id );
+                       if ($mw->match($matches[1])) {
+                               $text = $this->getVariableValue( $id );
+                               $this->mOutput->mContainsOldMagic = true;
+                       } else {
+                               $text = $matches[0];
+                       }
                } else {
                        $text = $matches[0];
                }
@@ -2598,31 +2841,6 @@ class Parser
                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);
-
-               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.
@@ -2635,9 +2853,10 @@ class Parser
         * @private
         */
        function braceSubstitution( $piece ) {
-               global $wgContLang, $wgLang, $wgAllowDisplayTitle, $action;
-               $fname = 'Parser::braceSubstitution';
+               global $wgContLang, $wgLang, $wgAllowDisplayTitle;
+               $fname = __METHOD__ /*. '-L' . count( $this->mArgStack )*/;
                wfProfileIn( $fname );
+               wfProfileIn( __METHOD__.'-setup' );
 
                # Flags
                $found = false;             # $text has been filled
@@ -2645,6 +2864,7 @@ class Parser
                $noparse = false;           # Unsafe HTML tags should not be stripped, etc.
                $noargs = false;            # Don't replace triple-brace arguments in $text
                $replaceHeadings = false;   # Make the edit section links go to the template not the article
+                $headingOffset = 0;         # Skip headings when number, to account for those that weren't transcluded.
                $isHTML = false;            # $text is HTML, armour it against wikitext transformation
                $forceRawInterwiki = false; # Force interwiki transclusion to be done in raw mode not rendered
 
@@ -2653,10 +2873,11 @@ class Parser
 
                $linestart = '';
 
+
                # $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 = $piece['title'];
+               $titleText = $part1 = $piece['title'];
                # If the third subpattern matched anything, it will start with |
 
                if (null == $piece['parts']) {
@@ -2670,12 +2891,13 @@ class Parser
                }
 
                $args = (null == $piece['parts']) ? array() : $piece['parts'];
-               $argc = count( $args );
+               wfProfileOut( __METHOD__.'-setup' );
 
                # SUBST
+               wfProfileIn( __METHOD__.'-modifiers' );
                if ( !$found ) {
                        $mwSubst =& MagicWord::get( 'subst' );
-                       if ( $mwSubst->matchStartAndRemove( $part1 ) xor ($this->mOutputType == OT_WIKI) ) {
+                       if ( $mwSubst->matchStartAndRemove( $part1 ) xor $this->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
@@ -2687,7 +2909,7 @@ class Parser
                        }
                }
 
-               # MSG, MSGNW, INT and RAW
+               # MSG, MSGNW and RAW
                if ( !$found ) {
                        # Check for MSGNW:
                        $mwMsgnw =& MagicWord::get( 'msgnw' );
@@ -2698,27 +2920,19 @@ class Parser
                                $mwMsg =& MagicWord::get( 'msg' );
                                $mwMsg->matchStartAndRemove( $part1 );
                        }
-                       
+
                        # Check for RAW:
                        $mwRaw =& MagicWord::get( 'raw' );
                        if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
                                $forceRawInterwiki = true;
                        }
-                       
-                       # Check if it is an internal message
-                       $mwInt =& MagicWord::get( 'int' );
-                       if ( $mwInt->matchStartAndRemove( $part1 ) ) {
-                               if ( $this->incrementIncludeCount( 'int:'.$part1 ) ) {
-                                       $text = $linestart . wfMsgReal( $part1, $args, true );
-                                       $found = true;
-                               }
-                       }
                }
+               wfProfileOut( __METHOD__.'-modifiers' );
 
                # Parser functions
                if ( !$found ) {
                        wfProfileIn( __METHOD__ . '-pfunc' );
-                       
+
                        $colonPos = strpos( $part1, ':' );
                        if ( $colonPos !== false ) {
                                # Case sensitive functions
@@ -2774,9 +2988,8 @@ class Parser
                                $noargs = true;
                                $found = true;
                                $text = $linestart .
-                                       '{{' . $part1 . '}}' .
-                                       '<!-- WARNING: template loop detected -->';
-                               wfDebug( "$fname: template loop broken at '$part1'\n" );
+                                       "[[$part1]]<!-- WARNING: template loop detected -->";
+                               wfDebug( __METHOD__.": template loop broken at '$part1'\n" );
                        } else {
                                # set $text to cached message.
                                $text = $linestart . $this->mTemplates[$piece['title']];
@@ -2800,43 +3013,39 @@ class Parser
 
 
                        if ( !is_null( $title ) ) {
-                               $checkVariantLink = sizeof($wgContLang->getVariants())>1;
+                               $titleText = $title->getPrefixedText();
                                # Check for language variants if the template is not found
-                               if($checkVariantLink && $title->getArticleID() == 0){
+                               if($wgContLang->hasVariants() && $title->getArticleID() == 0){
                                        $wgContLang->findVariantLink($part1, $title);
                                }
 
                                if ( !$title->isExternal() ) {
-                                       # Check for excessive inclusion
-                                       $dbk = $title->getPrefixedDBkey();
-                                       if ( $this->incrementIncludeCount( $dbk ) ) {
-                                               if ( $title->getNamespace() == NS_SPECIAL && $this->mOptions->getAllowSpecialInclusion() && $this->mOutputType != OT_WIKI ) {
-                                                       $text = SpecialPage::capturePath( $title );
-                                                       if ( is_string( $text ) ) {
-                                                               $found = true;
-                                                               $noparse = true;
-                                                               $noargs = true;
-                                                               $isHTML = true;
-                                                               $this->disableCache();
-                                                       }
-                                               } else {
-                                                       $articleContent = $this->fetchTemplate( $title );
-                                                       if ( $articleContent !== false ) {
-                                                               $found = true;
-                                                               $text = $articleContent;
-                                                               $replaceHeadings = true;
-                                                       }
+                                       if ( $title->getNamespace() == NS_SPECIAL && $this->mOptions->getAllowSpecialInclusion() && $this->ot['html'] ) {
+                                               $text = SpecialPage::capturePath( $title );
+                                               if ( is_string( $text ) ) {
+                                                       $found = true;
+                                                       $noparse = true;
+                                                       $noargs = true;
+                                                       $isHTML = true;
+                                                       $this->disableCache();
+                                               }
+                                       } else {
+                                               $articleContent = $this->fetchTemplate( $title );
+                                               if ( $articleContent !== false ) {
+                                                       $found = true;
+                                                       $text = $articleContent;
+                                                       $replaceHeadings = true;
                                                }
                                        }
 
                                        # If the title is valid but undisplayable, make a link to it
-                                       if ( $this->mOutputType == OT_HTML && !$found ) {
-                                               $text = '[['.$title->getPrefixedText().']]';
+                                       if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
+                                               $text = "[[:$titleText]]";
                                                $found = true;
                                        }
                                } elseif ( $title->isTrans() ) {
                                        // Interwiki transclusion
-                                       if ( $this->mOutputType == OT_HTML && !$forceRawInterwiki ) {
+                                       if ( $this->ot['html'] && !$forceRawInterwiki ) {
                                                $text = $this->interwikiTransclude( $title, 'render' );
                                                $isHTML = true;
                                                $noparse = true;
@@ -2862,11 +3071,19 @@ class Parser
                        wfProfileOut( __METHOD__ . '-loadtpl' );
                }
 
+               if ( $found && !$this->incrementIncludeSize( 'pre-expand', strlen( $text ) ) ) {
+                       # Error, oversize inclusion
+                       $text = $linestart .
+                               "[[$titleText]]<!-- WARNING: template omitted, pre-expand include size too large -->";
+                       $noparse = true;
+                       $noargs = true;
+               }
+
                # Recursive parsing, escaping and link table handling
                # Only for HTML output
-               if ( $nowiki && $found && $this->mOutputType == OT_HTML ) {
+               if ( $nowiki && $found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
                        $text = wfEscapeWikiText( $text );
-               } elseif ( ($this->mOutputType == OT_HTML || $this->mOutputType == OT_WIKI) && $found ) {
+               } elseif ( !$this->ot['msg'] && $found ) {
                        if ( $noargs ) {
                                $assocArgs = array();
                        } else {
@@ -2896,25 +3113,29 @@ class Parser
                        if ( !$noparse ) {
                                # If there are any <onlyinclude> tags, only include them
                                if ( in_string( '<onlyinclude>', $text ) && in_string( '</onlyinclude>', $text ) ) {
-                                       preg_match_all( '/<onlyinclude>(.*?)\n?<\/onlyinclude>/s', $text, $m );
-                                       $text = '';
-                                       foreach ($m[1] as $piece)
-                                               $text .= $piece;
+                                       $replacer = new OnlyIncludeReplacer;
+                                       StringUtils::delimiterReplaceCallback( '<onlyinclude>', '</onlyinclude>', 
+                                               array( &$replacer, 'replace' ), $text );
+                                       $text = $replacer->output;
                                }
                                # Remove <noinclude> sections and <includeonly> tags
-                               $text = preg_replace( '/<noinclude>.*?<\/noinclude>/s', '', $text );
+                               $text = StringUtils::delimiterReplace( '<noinclude>', '</noinclude>', '', $text );
                                $text = strtr( $text, array( '<includeonly>' => '' , '</includeonly>' => '' ) );
 
-                               if( $this->mOutputType == OT_HTML ) {
+                               if( $this->ot['html'] || $this->ot['pre'] ) {
                                        # Strip <nowiki>, <pre>, etc.
                                        $text = $this->strip( $text, $this->mStripState );
-                                       $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'replaceVariables' ), $assocArgs );
+                                       if ( $this->ot['html'] ) {
+                                               $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'replaceVariables' ), $assocArgs );
+                                       } elseif ( $this->ot['pre'] && $this->mOptions->getRemoveComments() ) {
+                                               $text = Sanitizer::removeHTMLcomments( $text );
+                                       }
                                }
                                $text = $this->replaceVariables( $text, $assocArgs );
 
                                # If the template begins with a table or block-level
                                # element, it should be treated as beginning a new line.
-                               if (!$piece['lineStart'] && preg_match('/^({\\||:|;|#|\*)/', $text)) {
+                               if (!$piece['lineStart'] && preg_match('/^({\\||:|;|#|\*)/', $text)) /*}*/{
                                        $text = "\n" . $text;
                                }
                        } elseif ( !$noargs ) {
@@ -2927,6 +3148,14 @@ class Parser
                # Prune lower levels off the recursion check path
                $this->mTemplatePath = $lastPathLevel;
 
+               if ( $found && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
+                       # Error, oversize inclusion
+                       $text = $linestart .
+                               "[[$titleText]]<!-- WARNING: template omitted, post-expand include size too large -->";
+                       $noparse = true;
+                       $noargs = true;
+               }
+
                if ( !$found ) {
                        wfProfileOut( $fname );
                        return $piece['text'];
@@ -2940,7 +3169,7 @@ class Parser
                        } else {
                                # replace ==section headers==
                                # XXX this needs to go away once we have a better parser.
-                               if ( $this->mOutputType != OT_WIKI && $replaceHeadings ) {
+                               if ( !$this->ot['wiki'] && !$this->ot['pre'] && $replaceHeadings ) {
                                        if( !is_null( $title ) )
                                                $encodedname = base64_encode($title->getPrefixedDBkey());
                                        else
@@ -2948,7 +3177,7 @@ class Parser
                                        $m = preg_split('/(^={1,6}.*?={1,6}\s*?$)/m', $text, -1,
                                                PREG_SPLIT_DELIM_CAPTURE);
                                        $text = '';
-                                       $nsec = 0;
+                                       $nsec = $headingOffset;
                                        for( $i = 0; $i < count($m); $i += 2 ) {
                                                $text .= $m[$i];
                                                if (!isset($m[$i + 1]) || $m[$i + 1] == "") continue;
@@ -2957,6 +3186,7 @@ class Parser
                                                        $text .= $hl;
                                                        continue;
                                                }
+                                               $m2 = array();
                                                preg_match('/^(={1,6})(.*?)(={1,6})\s*?$/m', $hl, $m2);
                                                $text .= $m2[1] . $m2[2] . "<!--MWTEMPLATESECTION="
                                                        . $encodedname . "&" . base64_encode("$nsec") . "-->" . $m2[3];
@@ -3064,25 +3294,31 @@ class Parser
 
                if ( array_key_exists( $arg, $inputArgs ) ) {
                        $text = $inputArgs[$arg];
-               } else if ($this->mOutputType == OT_HTML && null != $matches['parts'] && count($matches['parts']) > 0) {
+               } else if (($this->mOutputType == OT_HTML || $this->mOutputType == OT_PREPROCESS ) &&
+               null != $matches['parts'] && count($matches['parts']) > 0) {
                        $text = $matches['parts'][0];
                }
+               if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
+                       $text = $matches['text'] .
+                               '<!-- WARNING: argument omitted, expansion size too large -->';
+               }
 
                return $text;
        }
 
        /**
-        * Returns true if the function is allowed to include this entity
-        * @private
+        * Increment an include size counter
+        *
+        * @param string $type The type of expansion
+        * @param integer $size The size of the text
+        * @return boolean False if this inclusion would take it over the maximum, true otherwise
         */
-       function incrementIncludeCount( $dbk ) {
-               if ( !array_key_exists( $dbk, $this->mIncludeCount ) ) {
-                       $this->mIncludeCount[$dbk] = 0;
-               }
-               if ( ++$this->mIncludeCount[$dbk] <= MAX_INCLUDE_REPEAT ) {
-                       return true;
-               } else {
+       function incrementIncludeSize( $type, $size ) {
+               if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
                        return false;
+               } else {
+                       $this->mIncludeSizes[$type] += $size;
+                       return true;
                }
        }
 
@@ -3153,6 +3389,7 @@ class Parser
 
                # 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
+               $matches = array();
                $numMatches = preg_match_all( '/<H([1-6])(.*?'.'>)(.*?)<\/H[1-6] *>/i', $text, $matches );
 
                # if there are fewer than 4 headlines in the article, do not show TOC
@@ -3204,7 +3441,7 @@ class Parser
                        $templatetitle = '';
                        $templatesection = 0;
                        $numbering = '';
-
+                       $mat = array();
                        if (preg_match("/<!--MWTEMPLATESECTION=([^&]+)&([^_]+)-->/", $headline, $mat)) {
                                $istemplate = 1;
                                $templatetitle = base64_decode($mat[1]);
@@ -3277,8 +3514,7 @@ class Parser
 
                        # The canonized header is a version of the header text safe to use for links
                        # Avoid insertion of weird stuff like <math> by expanding the relevant sections
-                       $canonized_headline = $this->unstrip( $headline, $this->mStripState );
-                       $canonized_headline = $this->unstripNoWiki( $canonized_headline, $this->mStripState );
+                       $canonized_headline = $this->mStripState->unstripBoth( $headline );
 
                        # Remove link placeholders by the link text.
                        #     <!--LINK number-->
@@ -3300,7 +3536,7 @@ class Parser
                        $refers[$headlineCount] = $canonized_headline;
 
                        # count how many in assoc. array so we can track dupes in anchors
-                       @$refers[$canonized_headline]++;
+                       isset( $refers[$canonized_headline] ) ? $refers[$canonized_headline]++ : $refers[$canonized_headline] = 1;
                        $refcount[$headlineCount]=$refers[$canonized_headline];
 
                        # Don't number the heading if it is the only one (looks silly)
@@ -3317,6 +3553,9 @@ class Parser
                        if( $enoughToc && ( !isset($wgMaxTocLevel) || $toclevel<$wgMaxTocLevel ) ) {
                                $toc .= $sk->tocLine($anchor, $tocline, $numbering, $toclevel);
                        }
+                       # give headline the correct <h#> tag
+                       $head[$headlineCount] = "<a name=\"$anchor\"></a><h".$level.$matches[2][$headlineCount];
+
                        if( $showEditLink && ( !$istemplate || $templatetitle !== "" ) ) {
                                if ( empty( $head[$headlineCount] ) ) {
                                        $head[$headlineCount] = '';
@@ -3326,9 +3565,11 @@ class Parser
                                else
                                        $head[$headlineCount] .= $sk->editSectionLink($this->mTitle, $sectionCount+1, $headline_hint);
                        }
-
-                       # give headline the correct <h#> tag
-                       @$head[$headlineCount] .= "<a name=\"$anchor\"></a><h".$level.$matches[2][$headlineCount] .$headline.'</h'.$level.'>';
+                       // Yes, the headline logically goes before the edit section.  Why isn't it there
+                       // in source?  Ask the CSS people.  The float gets screwed up if you do that.
+                       // This might be moved to before the editsection at some point so that it will
+                       // display a bit more prettily without CSS, so please don't rely on the order.
+                       $head[$headlineCount] .= ' <span class="mw-headline">'.$headline.'</span></h'.$level.'>';
 
                        $headlineCount++;
                        if( !$istemplate )
@@ -3374,137 +3615,6 @@ class Parser
                }
        }
 
-       /**
-        * Return an HTML link for the "ISBN 123456" text
-        * @private
-        */
-       function magicISBN( $text ) {
-               $fname = 'Parser::magicISBN';
-               wfProfileIn( $fname );
-
-               $a = split( 'ISBN ', ' '.$text );
-               if ( count ( $a ) < 2 ) {
-                       wfProfileOut( $fname );
-                       return $text;
-               }
-               $text = substr( array_shift( $a ), 1);
-               $valid = '0123456789-Xx';
-
-               foreach ( $a as $x ) {
-                       # hack: don't replace inside thumbnail title/alt
-                       # attributes
-                       if(preg_match('/<[^>]+(alt|title)="[^">]*$/', $text)) {
-                               $text .= "ISBN $x";
-                               continue;
-                       }
-
-                       $isbn = $blank = '' ;
-                       while ( $x !== '' && ' ' == $x{0} ) {
-                               $blank .= ' ';
-                               $x = substr( $x, 1 );
-                       }
-                       if ( $x == '' ) { # blank isbn
-                               $text .= "ISBN $blank";
-                               continue;
-                       }
-                       while ( strstr( $valid, $x{0} ) != false ) {
-                               $isbn .= $x{0};
-                               $x = substr( $x, 1 );
-                       }
-                       $num = str_replace( '-', '', $isbn );
-                       $num = str_replace( ' ', '', $num );
-                       $num = str_replace( 'x', 'X', $num );
-
-                       if ( '' == $num ) {
-                               $text .= "ISBN $blank$x";
-                       } else {
-                               $titleObj = Title::makeTitle( NS_SPECIAL, 'Booksources' );
-                               $text .= '<a href="' .
-                                       $titleObj->escapeLocalUrl( 'isbn='.$num ) .
-                                       "\" class=\"internal\">ISBN $isbn</a>";
-                               $text .= $x;
-                       }
-               }
-               wfProfileOut( $fname );
-               return $text;
-       }
-
-       /**
-        * Return an HTML link for the "RFC 1234" text
-        *
-        * @private
-        * @param string $text     Text to be processed
-        * @param string $keyword  Magic keyword to use (default RFC)
-        * @param string $urlmsg   Interface message to use (default rfcurl)
-        * @return string
-        */
-       function magicRFC( $text, $keyword='RFC ', $urlmsg='rfcurl'  ) {
-
-               $valid = '0123456789';
-               $internal = false;
-
-               $a = split( $keyword, ' '.$text );
-               if ( count ( $a ) < 2 ) {
-                       return $text;
-               }
-               $text = substr( array_shift( $a ), 1);
-
-               /* Check if keyword is preceed by [[.
-                * This test is made here cause of the array_shift above
-                * that prevent the test to be done in the foreach.
-                */
-               if ( substr( $text, -2 ) == '[[' ) {
-                       $internal = true;
-               }
-
-               foreach ( $a as $x ) {
-                       /* token might be empty if we have RFC RFC 1234 */
-                       if ( $x=='' ) {
-                               $text.=$keyword;
-                               continue;
-                               }
-
-                       # hack: don't replace inside thumbnail title/alt
-                       # attributes
-                       if(preg_match('/<[^>]+(alt|title)="[^">]*$/', $text)) {
-                               $text .= $keyword . $x;
-                               continue;
-                       }
-
-                       $id = $blank = '' ;
-
-                       /** remove and save whitespaces in $blank */
-                       while ( $x{0} == ' ' ) {
-                               $blank .= ' ';
-                               $x = substr( $x, 1 );
-                       }
-
-                       /** remove and save the rfc number in $id */
-                       while ( strstr( $valid, $x{0} ) != false ) {
-                               $id .= $x{0};
-                               $x = substr( $x, 1 );
-                       }
-
-                       if ( $id == '' ) {
-                               /* call back stripped spaces*/
-                               $text .= $keyword.$blank.$x;
-                       } elseif( $internal ) {
-                               /* normal link */
-                               $text .= $keyword.$id.$x;
-                       } else {
-                               /* build the external link*/
-                               $url = wfMsg( $urlmsg, $id);
-                               $sk =& $this->mOptions->getSkin();
-                               $la = $sk->getExternalLinkAttributes( $url, $keyword.$id );
-                               $text .= "<a href=\"{$url}\"{$la}>{$keyword}{$id}</a>{$x}";
-                       }
-
-                       /* Check if the next RFC keyword is preceed by [[ */
-                       $internal = ( substr($x,-2) == '[[' );
-               }
-               return $text;
-       }
-
        /**
         * Transform wiki markup when saving a page by doing \r\n -> \n
         * conversion, substitting signatures, {{subst:}} templates, etc.
@@ -3517,24 +3627,23 @@ class Parser
         * @return string the altered wiki markup
         * @public
         */
-       function preSaveTransform( $text, &$title, &$user, $options, $clearState = true ) {
+       function preSaveTransform( $text, &$title, $user, $options, $clearState = true ) {
                $this->mOptions = $options;
                $this->mTitle =& $title;
-               $this->mOutputType = OT_WIKI;
+               $this->setOutputType( OT_WIKI );
 
                if ( $clearState ) {
                        $this->clearState();
                }
 
-               $stripState = false;
+               $stripState = new StripState;
                $pairs = array(
                        "\r\n" => "\n",
                );
                $text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text );
                $text = $this->strip( $text, $stripState, true, array( 'gallery' ) );
                $text = $this->pstPass2( $text, $stripState, $user );
-               $text = $this->unstrip( $text, $stripState );
-               $text = $this->unstripNoWiki( $text, $stripState );
+               $text = $stripState->unstripBoth( $text );
                return $text;
        }
 
@@ -3542,7 +3651,7 @@ class Parser
         * Pre-save transform helper function
         * @private
         */
-       function pstPass2( $text, &$stripState, &$user ) {
+       function pstPass2( $text, &$stripState, $user ) {
                global $wgContLang, $wgLocaltimezone;
 
                /* Note: This is the timestamp saved as hardcoded wikitext to
@@ -3579,28 +3688,25 @@ class Parser
                #
                global $wgLegalTitleChars;
                $tc = "[$wgLegalTitleChars]";
-               $np = str_replace( array( '(', ')' ), array( '', '' ), $tc ); # No parens
+               $nc = '[ _0-9A-Za-z\x80-\xff]'; # Namespaces can use non-ascii!
 
-               $namespacechar = '[ _0-9A-Za-z\x80-\xff]'; # Namespaces can use non-ascii!
-               $conpat = "/^({$np}+) \\(({$tc}+)\\)$/";
+               $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( \\($tc+\\))\\|]]/";            # [[ns:page (context)|]]
+               $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( \\($tc+\\)|)(, $tc+|)\\|]]/";  # [[ns:page (context), context|]]
+               $p2 = "/\[\[\\|($tc+)]]/";                                      # [[|page]]
 
-               $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 );
+               # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
+               $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
+               $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
 
-               if ( '' == $context ) {
-                       $text = preg_replace( $p2, '[[\\1]]', $text );
+               $t = $this->mTitle->getText();
+               $m = array();
+               if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
+                       $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
+               } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && '' != "$m[1]$m[2]" ) {
+                       $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
                } else {
-                       $text = preg_replace( $p2, "[[\\1 ({$context})|\\1]]", $text );
+                       # if there's no context, don't bother duplicating the title
+                       $text = preg_replace( $p2, '[[\\1]]', $text );
                }
 
                # Trim trailing whitespace
@@ -3700,7 +3806,7 @@ class Parser
        function startExternalParse( &$title, $options, $outputType, $clearState = true ) {
                $this->mTitle =& $title;
                $this->mOptions = $options;
-               $this->mOutputType = $outputType;
+               $this->setOutputType( $outputType );
                if ( $clearState ) {
                        $this->clearState();
                }
@@ -3728,9 +3834,13 @@ class Parser
 
                wfProfileIn($fname);
 
-               $this->mTitle = $wgTitle;
+               if ( $wgTitle ) {
+                       $this->mTitle = $wgTitle;
+               } else {
+                       $this->mTitle = Title::newFromText('msg');
+               }
                $this->mOptions = $options;
-               $this->mOutputType = OT_MSG;
+               $this->setOutputType( OT_MSG );
                $this->clearState();
                $text = $this->replaceVariables( $text );
 
@@ -3756,7 +3866,7 @@ class Parser
         */
        function setHook( $tag, $callback ) {
                $tag = strtolower( $tag );
-               $oldVal = @$this->mTagHooks[$tag];
+               $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
                $this->mTagHooks[$tag] = $callback;
 
                return $oldVal;
@@ -3767,10 +3877,10 @@ class Parser
         * The callback function should have the form:
         *    function myParserFunction( &$parser, $arg1, $arg2, $arg3 ) { ... }
         *
-        * The callback may either return the text result of the function, or an array with the text 
-        * in element 0, and a number of flags in the other elements. The names of the flags are 
+        * The callback may either return the text result of the function, or an array with the text
+        * in element 0, and a number of flags in the other elements. The names of the flags are
         * specified in the keys. Valid flags are:
-        *   found                     The text returned is valid, stop processing the template. This 
+        *   found                     The text returned is valid, stop processing the template. This
         *                             is on by default.
         *   nowiki                    Wiki markup in the return value should be escaped
         *   noparse                   Unsafe HTML tags should not be stripped, etc.
@@ -3779,23 +3889,21 @@ class Parser
         *
         * @public
         *
-        * @param mixed $id The magic word ID
+        * @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: 
+        * @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, $flags = 0 ) {
-               $oldVal = @$this->mFunctionHooks[$id];
+               $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id] : null;
                $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.' );
-               }
+               if( !$mw )
+                       throw new MWException( 'Parser::setFunctionHook() expecting a magic word identifier.' );
 
                $synonyms = $mw->getSynonyms();
                $sensitive = intval( $mw->isCaseSensitive() );
@@ -3818,6 +3926,15 @@ class Parser
                return $oldVal;
        }
 
+       /**
+        * Get all registered function hook identifiers
+        *
+        * @return array
+        */
+       function getFunctionHooks() {
+               return array_keys( $this->mFunctionHooks );
+       }
+
        /**
         * Replace <!--LINK--> link placeholders with actual links, in the buffer
         * Placeholders created in Skin::makeLinkObj()
@@ -3829,7 +3946,7 @@ class Parser
         */
        function replaceLinkHolders( &$text, $options = 0 ) {
                global $wgUser;
-               global $wgOutputReplace;
+               global $wgContLang;
 
                $fname = 'Parser::replaceLinkHolders';
                wfProfileIn( $fname );
@@ -3850,6 +3967,7 @@ class Parser
 
                        # Generate query
                        $query = false;
+                       $current = null;
                        foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) {
                                # Make title object
                                $title = $this->mLinkHolders['titles'][$key];
@@ -3920,9 +4038,135 @@ class Parser
                        }
                        wfProfileOut( $fname.'-check' );
 
+                       # Do a second query for different language variants of links and categories
+                       if($wgContLang->hasVariants()){
+                               $linkBatch = new LinkBatch();
+                               $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders)
+                               $categoryMap = array(); // maps $category_variant => $category (dbkeys)
+                               $varCategories = array(); // category replacements oldDBkey => newDBkey
+
+                               $categories = $this->mOutput->getCategoryLinks();
+
+                               // Add variants of links to link batch
+                               foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) {
+                                       $title = $this->mLinkHolders['titles'][$key];
+                                       if ( is_null( $title ) )
+                                               continue;
+
+                                       $pdbk = $title->getPrefixedDBkey();
+                                       $titleText = $title->getText();
+
+                                       // generate all variants of the link title text
+                                       $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText);
+
+                                       // if link was not found (in first query), add all variants to query
+                                       if ( !isset($colours[$pdbk]) ){
+                                               foreach($allTextVariants as $textVariant){
+                                                       if($textVariant != $titleText){
+                                                               $variantTitle = Title::makeTitle( $ns, $textVariant );
+                                                               if(is_null($variantTitle)) continue;
+                                                               $linkBatch->addObj( $variantTitle );
+                                                               $variantMap[$variantTitle->getPrefixedDBkey()][] = $key;
+                                                       }
+                                               }
+                                       }
+                               }
+
+                               // process categories, check if a category exists in some variant
+                               foreach( $categories as $category){
+                                       $variants = $wgContLang->convertLinkToAllVariants($category);
+                                       foreach($variants as $variant){
+                                               $variantTitle = Title::newFromDBkey( Title::makeName(NS_CATEGORY,$variant) );
+                                               if(is_null($variantTitle)) continue;
+                                               $linkBatch->addObj( $variantTitle );
+                                               $categoryMap[$variant] = $category;
+                                       }
+                               }
+
+
+                               if(!$linkBatch->isEmpty()){
+                                       // 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 .= " FROM $page WHERE $titleClause";
+                                       if ( $options & RLH_FOR_UPDATE ) {
+                                               $variantQuery .= ' FOR UPDATE';
+                                       }
+
+                                       $varRes = $dbr->query( $variantQuery, $fname );
+
+                                       // for each found variants, figure out link holders and replace
+                                       while ( $s = $dbr->fetchObject($varRes) ) {
+
+                                               $variantTitle = Title::makeTitle( $s->page_namespace, $s->page_title );
+                                               $varPdbk = $variantTitle->getPrefixedDBkey();
+                                               $vardbk = $variantTitle->getDBkey();
+                                               $linkCache->addGoodLinkObj( $s->page_id, $variantTitle );
+                                               $this->mOutput->addLink( $variantTitle, $s->page_id );
+
+                                               $holderKeys = array();
+                                               if(isset($variantMap[$varPdbk]))
+                                                       $holderKeys = $variantMap[$varPdbk];
+
+                                               // loop over link holders
+                                               foreach($holderKeys as $key){
+                                                       $title = $this->mLinkHolders['titles'][$key];
+                                                       if ( is_null( $title ) ) continue;
+
+                                                       $pdbk = $title->getPrefixedDBkey();
+
+                                                       if(!isset($colours[$pdbk])){
+                                                               // found link in some of the variants, replace the link holder data
+                                                               $this->mLinkHolders['titles'][$key] = $variantTitle;
+                                                               $this->mLinkHolders['dbkeys'][$key] = $variantTitle->getDBkey();
+
+                                                               // set pdbk and colour
+                                                               $pdbks[$key] = $varPdbk;
+                                                               if ( $threshold >  0 ) {
+                                                                       $size = $s->page_len;
+                                                                       if ( $s->page_is_redirect || $s->page_namespace != 0 || $size >= $threshold ) {
+                                                                               $colours[$varPdbk] = 1;
+                                                                       } else {
+                                                                               $colours[$varPdbk] = 2;
+                                                                       }
+                                                               }
+                                                               else {
+                                                                       $colours[$varPdbk] = 1;
+                                                               }
+                                                       }
+                                               }
+
+                                               // check if the object is a variant of a category
+                                               if(isset($categoryMap[$vardbk])){
+                                                       $oldkey = $categoryMap[$vardbk];
+                                                       if($oldkey != $vardbk)
+                                                               $varCategories[$oldkey]=$vardbk;                                                        
+                                               }                                               
+                                       }
+
+                                       // rebuild the categories in original order (if there are replacements)
+                                       if(count($varCategories)>0){
+                                               $newCats = array();
+                                               $originalCats = $this->mOutput->getCategories();
+                                               foreach($originalCats as $cat => $sortkey){
+                                                       // make the replacement
+                                                       if( array_key_exists($cat,$varCategories) )
+                                                               $newCats[$varCategories[$cat]] = $sortkey;
+                                                       else $newCats[$cat] = $sortkey;
+                                               }
+                                               $this->mOutput->setCategoryLinks($newCats);
+                                       }
+                               }
+                       }
+
                        # Construct search and replace arrays
                        wfProfileIn( $fname.'-construct' );
-                       $wgOutputReplace = array();
+                       $replacePairs = array();
                        foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) {
                                $pdbk = $pdbks[$key];
                                $searchkey = "<!--LINK $key-->";
@@ -3931,27 +4175,27 @@ class Parser
                                        $linkCache->addBadLinkObj( $title );
                                        $colours[$pdbk] = 0;
                                        $this->mOutput->addLink( $title, 0 );
-                                       $wgOutputReplace[$searchkey] = $sk->makeBrokenLinkObj( $title,
+                                       $replacePairs[$searchkey] = $sk->makeBrokenLinkObj( $title,
                                                                        $this->mLinkHolders['texts'][$key],
                                                                        $this->mLinkHolders['queries'][$key] );
                                } elseif ( $colours[$pdbk] == 1 ) {
-                                       $wgOutputReplace[$searchkey] = $sk->makeKnownLinkObj( $title,
+                                       $replacePairs[$searchkey] = $sk->makeKnownLinkObj( $title,
                                                                        $this->mLinkHolders['texts'][$key],
                                                                        $this->mLinkHolders['queries'][$key] );
                                } elseif ( $colours[$pdbk] == 2 ) {
-                                       $wgOutputReplace[$searchkey] = $sk->makeStubLinkObj( $title,
+                                       $replacePairs[$searchkey] = $sk->makeStubLinkObj( $title,
                                                                        $this->mLinkHolders['texts'][$key],
                                                                        $this->mLinkHolders['queries'][$key] );
                                }
                        }
+                       $replacer = new HashtableReplacer( $replacePairs, 1 );
                        wfProfileOut( $fname.'-construct' );
 
                        # Do the thing
                        wfProfileIn( $fname.'-replace' );
-
                        $text = preg_replace_callback(
                                '/(<!--LINK .*?-->)/',
-                               "wfOutputReplaceMatches",
+                               $replacer->cb(),
                                $text);
 
                        wfProfileOut( $fname.'-replace' );
@@ -3962,15 +4206,16 @@ class Parser
                if ( !empty( $this->mInterwikiLinkHolders['texts'] ) ) {
                        wfProfileIn( $fname.'-interwiki' );
                        # Make interwiki link HTML
-                       $wgOutputReplace = array();
+                       $replacePairs = array();
                        foreach( $this->mInterwikiLinkHolders['texts'] as $key => $link ) {
                                $title = $this->mInterwikiLinkHolders['titles'][$key];
-                               $wgOutputReplace[$key] = $sk->makeLinkObj( $title, $link );
+                               $replacePairs[$key] = $sk->makeLinkObj( $title, $link );
                        }
+                       $replacer = new HashtableReplacer( $replacePairs, 1 );
 
                        $text = preg_replace_callback(
                                '/<!--IWLINK (.*?)-->/',
-                               "wfOutputReplaceMatches",
+                               $replacer->cb(),
                                $text );
                        wfProfileOut( $fname.'-interwiki' );
                }
@@ -4021,13 +4266,13 @@ class Parser
        /**
         * Tag hook handler for 'pre'.
         */
-       function renderPreTag( $text, $attribs, $parser ) {
+       function renderPreTag( $text, $attribs ) {
                // Backwards-compatibility hack
-               $content = preg_replace( '!<nowiki>(.*?)</nowiki>!is', '\\1', $text );
+               $content = StringUtils::delimiterReplace( '<nowiki>', '</nowiki>', '$1', $text, 'i' );
 
                $attribs = Sanitizer::validateTagAttributes( $attribs, 'pre' );
                return wfOpenElement( 'pre', $attribs ) .
-                       wfEscapeHTMLTagsOnly( $content ) .
+                       Xml::escapeTagsOnly( $content ) .
                        '</pre>';
        }
 
@@ -4054,6 +4299,7 @@ class Parser
                foreach ( $lines as $line ) {
                        # match lines like these:
                        # Image:someimage.jpg|This is some image
+                       $matches = array();
                        preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
                        # Skip empty lines
                        if ( count( $matches ) == 0 ) {
@@ -4092,8 +4338,8 @@ class Parser
        /**
         * Parse image options text and use it to make an image
         */
-       function makeImage( &$nt, $options ) {
-               global $wgUseImageResize;
+       function makeImage( $nt, $options ) {
+               global $wgUseImageResize, $wgDjvuRenderer;
 
                $align = '';
 
@@ -4117,12 +4363,14 @@ class Parser
                $mwWidth  =& MagicWord::get( 'img_width' );
                $mwCenter =& MagicWord::get( 'img_center' );
                $mwFramed =& MagicWord::get( 'img_framed' );
+               $mwPage   =& MagicWord::get( 'img_page' );
                $caption = '';
 
                $width = $height = $framed = $thumb = false;
+               $page = null;
                $manual_thumb = '' ;
 
-               foreach( $part as $key => $val ) {
+               foreach( $part as $val ) {
                        if ( $wgUseImageResize && ! is_null( $mwThumb->matchVariableStartToEnd($val) ) ) {
                                $thumb=true;
                        } elseif ( ! is_null( $match = $mwManualThumb->matchVariableStartToEnd($val) ) ) {
@@ -4141,9 +4389,14 @@ class Parser
                        } elseif ( ! is_null( $mwNone->matchVariableStartToEnd($val) ) ) {
                                # remember to set an alignment, don't render immediately
                                $align = 'none';
+                       } elseif ( isset( $wgDjvuRenderer ) && $wgDjvuRenderer
+                                  && ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) {
+                               # Select a page in a multipage document
+                               $page = $match;
                        } elseif ( $wgUseImageResize && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
                                wfDebug( "img_width match: $match\n" );
                                # $match is the image width in pixels
+                               $m = array();
                                if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) {
                                        $width = intval( $m[1] );
                                        $height = intval( $m[2] );
@@ -4162,12 +4415,12 @@ class Parser
                # make sure there are no placeholders in thumbnail attributes
                # that are later expanded to html- so expand them now and
                # remove the tags
-               $alt = $this->unstrip($alt, $this->mStripState); 
+               $alt = $this->mStripState->unstripBoth( $alt );
                $alt = Sanitizer::stripAllTags( $alt );
 
                # Linker does the rest
                $sk =& $this->mOptions->getSkin();
-               return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $width, $height, $framed, $thumb, $manual_thumb );
+               return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $width, $height, $framed, $thumb, $manual_thumb, $page );
        }
 
        /**
@@ -4189,15 +4442,10 @@ class Parser
         */
        function attributeStripCallback( &$text, $args ) {
                $text = $this->replaceVariables( $text, $args );
-               $text = $this->unstripForHTML( $text );
+               $text = $this->mStripState->unstripBoth( $text );
                return $text;
        }
 
-       function unstripForHTML( $text ) {
-               $text = $this->unstrip( $text, $this->mStripState );
-               $text = $this->unstripNoWiki( $text, $this->mStripState );
-               return $text;
-       }
        /**#@-*/
 
        /**#@+
@@ -4233,16 +4481,16 @@ class Parser
        private function extractSections( $text, $section, $mode, $newtext='' ) {
                # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML
                # comments to be stripped as well)
-               $striparray = array();
+               $stripState = new StripState;
 
                $oldOutputType = $this->mOutputType;
                $oldOptions = $this->mOptions;
                $this->mOptions = new ParserOptions();
-               $this->mOutputType = OT_WIKI;
+               $this->setOutputType( OT_WIKI );
 
-               $striptext = $this->strip( $text, $striparray, true );
+               $striptext = $this->strip( $text, $stripState, true );
 
-               $this->mOutputType = $oldOutputType;
+               $this->setOutputType( $oldOutputType );
                $this->mOptions = $oldOptions;
 
                # now that we can be sure that no pseudo-sections are in the source,
@@ -4347,9 +4595,7 @@ class Parser
                        }
                }
                # reinsert stripped tags
-               $rv = $this->unstrip( $rv, $striparray );
-               $rv = $this->unstripNoWiki( $rv, $striparray );
-               $rv = trim( $rv );
+               $rv = trim( $stripState->unstripBoth( $rv ) );
                return $rv;
        }
 
@@ -4372,6 +4618,36 @@ class Parser
                return $this->extractSections( $oldtext, $section, "replace", $text );
        }
 
+       /**
+        * Get the timestamp associated with the current revision, adjusted for 
+        * the default server-local timestamp
+        */
+       function getRevisionTimestamp() {
+               if ( is_null( $this->mRevisionTimestamp ) ) {
+                       wfProfileIn( __METHOD__ );
+                       global $wgContLang;
+                       $dbr =& wfGetDB( DB_SLAVE );
+                       $timestamp = $dbr->selectField( 'revision', 'rev_timestamp',
+                                       array( 'rev_id' => $this->mRevisionId ), __METHOD__ );
+                       
+                       // Normalize timestamp to internal MW format for timezone processing.
+                       // This has the added side-effect of replacing a null value with
+                       // the current time, which gives us more sensible behavior for
+                       // previews.
+                       $timestamp = wfTimestamp( TS_MW, $timestamp );
+                       
+                       // The cryptic '' timezone parameter tells to use the site-default
+                       // timezone offset instead of the user settings.
+                       //
+                       // Since this value will be saved into the parser cache, served
+                       // to other users, and potentially even used inside links and such,
+                       // it needs to be consistent for all visitors.
+                       $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
+                       
+                       wfProfileOut( __METHOD__ );
+               }
+               return $this->mRevisionTimestamp;
+       }
 }
 
 /**
@@ -4442,7 +4718,7 @@ class ParserOutput
        function addImage( $name )           { $this->mImages[$name] = 1; }
        function addLanguageLink( $t )       { $this->mLanguageLinks[] = $t; }
        function addExternalLink( $url )     { $this->mExternalLinks[$url] = 1; }
-       
+
        function setNewSection( $value ) {
                $this->mNewSection = (bool)$value;
        }
@@ -4450,12 +4726,15 @@ class ParserOutput
                return (bool)$this->mNewSection;
        }
 
-       function addLink( $title, $id ) {
+       function addLink( $title, $id = null ) {
                $ns = $title->getNamespace();
                $dbk = $title->getDBkey();
                if ( !isset( $this->mLinks[$ns] ) ) {
                        $this->mLinks[$ns] = array();
                }
+               if ( is_null( $id ) ) {
+                       $id = $title->getArticleID();
+               }
                $this->mLinks[$ns][$dbk] = $id;
        }
 
@@ -4494,7 +4773,7 @@ class ParserOutput
  */
 class ParserOptions
 {
-       # All variables are private
+       # All variables are supposed to be private in theory, although in practise this is not the case.
        var $mUseTeX;                    # Use texvc to expand <math> tags
        var $mUseDynamicDates;           # Use DateFormatter to format dates
        var $mInterwikiMagic;            # Interlanguage links are removed and returned in an array
@@ -4507,6 +4786,8 @@ class ParserOptions
        var $mAllowSpecialInclusion;     # Allow inclusion of special pages
        var $mTidy;                      # Ask for tidy cleanup
        var $mInterfaceMessage;          # Which lang to call for PLURAL and GRAMMAR
+       var $mMaxIncludeSize;            # Maximum size of template expansions, in bytes
+       var $mRemoveComments;            # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
 
        var $mUser;                      # Stored user object, just used to initialise the skin
 
@@ -4520,6 +4801,8 @@ class ParserOptions
        function getAllowSpecialInclusion()         { return $this->mAllowSpecialInclusion; }
        function getTidy()                          { return $this->mTidy; }
        function getInterfaceMessage()              { return $this->mInterfaceMessage; }
+       function getMaxIncludeSize()                { return $this->mMaxIncludeSize; }
+       function getRemoveComments()                { return $this->mRemoveComments; }
 
        function &getSkin() {
                if ( !isset( $this->mSkin ) ) {
@@ -4528,7 +4811,7 @@ class ParserOptions
                return $this->mSkin;
        }
 
-       function getDateFormat() { 
+       function getDateFormat() {
                if ( !isset( $this->mDateFormat ) ) {
                        $this->mDateFormat = $this->mUser->getDatePreference();
                }
@@ -4545,8 +4828,10 @@ class ParserOptions
        function setNumberHeadings( $x )            { return wfSetVar( $this->mNumberHeadings, $x ); }
        function setAllowSpecialInclusion( $x )     { return wfSetVar( $this->mAllowSpecialInclusion, $x ); }
        function setTidy( $x )                      { return wfSetVar( $this->mTidy, $x); }
-       function setSkin( &$x ) { $this->mSkin =& $x; }
+       function setSkin( $x )                      { $this->mSkin = $x; }
        function setInterfaceMessage( $x )          { return wfSetVar( $this->mInterfaceMessage, $x); }
+       function setMaxIncludeSize( $x )            { return wfSetVar( $this->mMaxIncludeSize, $x ); }
+       function setRemoveComments( $x )            { return wfSetVar( $this->mRemoveComments, $x ); }
 
        function ParserOptions( $user = null ) {
                $this->initialiseFromUser( $user );
@@ -4563,7 +4848,7 @@ class ParserOptions
        /** Get user options */
        function initialiseFromUser( $userInput ) {
                global $wgUseTeX, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages;
-               global $wgAllowExternalImagesFrom, $wgAllowSpecialInclusion;
+               global $wgAllowExternalImagesFrom, $wgAllowSpecialInclusion, $wgMaxArticleSize;
                $fname = 'ParserOptions::initialiseFromUser';
                wfProfileIn( $fname );
                if ( !$userInput ) {
@@ -4572,7 +4857,6 @@ class ParserOptions
                                $user = $wgUser;
                        } else {
                                $user = new User;
-                               $user->setLoaded( true );
                        }
                } else {
                        $user =& $userInput;
@@ -4592,136 +4876,53 @@ class ParserOptions
                $this->mAllowSpecialInclusion = $wgAllowSpecialInclusion;
                $this->mTidy = false;
                $this->mInterfaceMessage = false;
+               $this->mMaxIncludeSize = $wgMaxArticleSize * 1024;
+               $this->mRemoveComments = true;
                wfProfileOut( $fname );
        }
 }
 
-/**
- * Callback function used by Parser::replaceLinkHolders()
- * to substitute link placeholders.
- */
-function &wfOutputReplaceMatches( $matches ) {
-       global $wgOutputReplace;
-       return $wgOutputReplace[$matches[1]];
-}
+class OnlyIncludeReplacer {
+       var $output = '';
 
-/**
- * Return the total number of articles
- */
-function wfNumberOfArticles() {
-       global $wgNumberOfArticles;
-
-       wfLoadSiteStats();
-       return $wgNumberOfArticles;
-}
-
-/**
- * Return the number of files
- */
-function wfNumberOfFiles() {
-       $fname = 'wfNumberOfFiles';
-
-       wfProfileIn( $fname );
-       $dbr =& wfGetDB( DB_SLAVE );
-       $numImages = $dbr->selectField('site_stats', 'ss_images', array(), $fname );
-       wfProfileOut( $fname );
-
-       return $numImages;
+       function replace( $matches ) { 
+               if ( substr( $matches[1], -1 ) == "\n" ) {
+                       $this->output .= substr( $matches[1], 0, -1 );
+               } else {
+                       $this->output .= $matches[1];
+               }
+       }
 }
 
-/**
- * Return the number of user accounts
- * @return integer
- */
-function wfNumberOfUsers() {
-       wfProfileIn( 'wfNumberOfUsers' );
-       $dbr =& wfGetDB( DB_SLAVE );
-       $count = $dbr->selectField( 'site_stats', 'ss_users', array(), 'wfNumberOfUsers' );
-       wfProfileOut( 'wfNumberOfUsers' );
-       return (int)$count;
-}
+class StripState {
+       var $general, $nowiki;
 
-/**
- * Return the total number of pages
- * @return integer
- */
-function wfNumberOfPages() {
-       wfProfileIn( 'wfNumberOfPages' );
-       $dbr =& wfGetDB( DB_SLAVE );
-       $count = $dbr->selectField( 'site_stats', 'ss_total_pages', array(), 'wfNumberOfPages' );
-       wfProfileOut( 'wfNumberOfPages' );
-       return (int)$count;
-}
-
-/**
- * Return the total number of admins
- *
- * @return integer
- */
-function wfNumberOfAdmins() {
-       static $admins = -1;
-       wfProfileIn( 'wfNumberOfAdmins' );
-       if( $admins == -1 ) {
-               $dbr =& wfGetDB( DB_SLAVE );
-               $admins = $dbr->selectField( 'user_groups', 'COUNT(*)', array( 'ug_group' => 'sysop' ), 'wfNumberOfAdmins' );
+       function __construct() {
+               $this->general = new ReplacementArray;
+               $this->nowiki = new ReplacementArray;
        }
-       wfProfileOut( 'wfNumberOfAdmins' );
-       return (int)$admins;
-}
 
-/**
- * Count the number of pages in a particular namespace
- *
- * @param $ns Namespace
- * @return integer
- */
-function wfPagesInNs( $ns ) {
-       static $pageCount = array();
-       wfProfileIn( 'wfPagesInNs' );
-       if( !isset( $pageCount[$ns] ) ) {
-               $dbr =& wfGetDB( DB_SLAVE );
-               $pageCount[$ns] = $dbr->selectField( 'page', 'COUNT(*)', array( 'page_namespace' => $ns ), 'wfPagesInNs' );
+       function unstripGeneral( $text ) {
+               wfProfileIn( __METHOD__ );
+               $text = $this->general->replace( $text );
+               wfProfileOut( __METHOD__ );
+               return $text;
        }
-       wfProfileOut( 'wfPagesInNs' );
-       return (int)$pageCount[$ns];
-}
 
-/**
- * Get various statistics from the database
- * @private
- */
-function wfLoadSiteStats() {
-       global $wgNumberOfArticles, $wgTotalViews, $wgTotalEdits;
-       $fname = 'wfLoadSiteStats';
-
-       if ( -1 != $wgNumberOfArticles ) return;
-       $dbr =& wfGetDB( DB_SLAVE );
-       $s = $dbr->selectRow( 'site_stats',
-               array( 'ss_total_views', 'ss_total_edits', 'ss_good_articles' ),
-               array( 'ss_row_id' => 1 ), $fname
-       );
-
-       if ( $s === false ) {
-               return;
-       } else {
-               $wgTotalViews = $s->ss_total_views;
-               $wgTotalEdits = $s->ss_total_edits;
-               $wgNumberOfArticles = $s->ss_good_articles;
+       function unstripNoWiki( $text ) {
+               wfProfileIn( __METHOD__ );
+               $text = $this->nowiki->replace( $text );
+               wfProfileOut( __METHOD__ );
+               return $text;
        }
-}
 
-/**
- * Escape html tags
- * Basically replacing " > and < with HTML entities ( &quot;, &gt;, &lt;)
- *
- * @param $in String: text that might contain HTML tags.
- * @return string Escaped string
- */
-function wfEscapeHTMLTagsOnly( $in ) {
-       return str_replace(
-               array( '"', '>', '<' ),
-               array( '&quot;', '&gt;', '&lt;' ),
-               $in );
+       function unstripBoth( $text ) {
+               wfProfileIn( __METHOD__ );
+               $text = $this->general->replace( $text );
+               $text = $this->nowiki->replace( $text );
+               wfProfileOut( __METHOD__ );
+               return $text;
+       }
 }
 
 ?>