apply stub threshold to all content namespaces
[lhc/web/wiklou.git] / includes / Parser.php
index f4b3751..966b82c 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+
 /**
+ *
  * File for Parser and related classes
  *
- * @package MediaWiki
- * @subpackage Parser
+ * @addtogroup Parser
  */
 
 /**
@@ -57,18 +58,21 @@ define( 'MW_COLON_STATE_COMMENTDASH', 6 );
 define( 'MW_COLON_STATE_COMMENTDASHDASH', 7 );
 
 /**
- * PHP Parser
- *
- * Processes wiki markup
+ * PHP Parser - Processes wiki markup (which uses a more user-friendly 
+ * syntax, such as "[[link]]" for making links), and provides a one-way
+ * transformation of that wiki markup it into XHTML output / markup
+ * (which in turn the browser understands, and can display).
  *
  * <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
@@ -84,21 +88,22 @@ define( 'MW_COLON_STATE_COMMENTDASHDASH', 7 );
  *  * only within ParserOptions
  * </pre>
  *
- * @package MediaWiki
+ * @addtogroup Parser
  */
 class Parser
 {
+       const VERSION = MW_PARSER_VERSION;
        /**#@+
         * @private
         */
        # Persistent:
        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 $mIncludeSizes, $mDefaultSort;
        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
@@ -110,7 +115,9 @@ class Parser
                $mTitle,        // Title context, used for self-link rendering and similar things
                $mOutputType,   // Output type, one of the OT_xxx constants
                $ot,            // Shortcut alias, see setOutputType()
-               $mRevisionId;   // ID to display in {{REVISIONID}} tags
+               $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
 
        /**#@-*/
 
@@ -125,7 +132,7 @@ class Parser
                $this->mFunctionSynonyms = array( 0 => array(), 1 => array() );
                $this->mFirstCall = true;
        }
-
+       
        /**
         * Do various kinds of initialisation on the first call of the parser
         */
@@ -133,12 +140,12 @@ class Parser
                if ( !$this->mFirstCall ) {
                        return;
                }
-
+               
                wfProfileIn( __METHOD__ );
                global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions;
-
+               
                $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 );
@@ -158,10 +165,13 @@ class Parser
                $this->setFunctionHook( 'numberofarticles', array( 'CoreParserFunctions', 'numberofarticles' ), SFH_NO_HASH );
                $this->setFunctionHook( 'numberoffiles', array( 'CoreParserFunctions', 'numberoffiles' ), SFH_NO_HASH );
                $this->setFunctionHook( 'numberofadmins', array( 'CoreParserFunctions', 'numberofadmins' ), SFH_NO_HASH );
+               $this->setFunctionHook( 'numberofedits', array( 'CoreParserFunctions', 'numberofedits' ), SFH_NO_HASH );
                $this->setFunctionHook( 'language', array( 'CoreParserFunctions', 'language' ), SFH_NO_HASH );
                $this->setFunctionHook( 'padleft', array( 'CoreParserFunctions', 'padleft' ), SFH_NO_HASH );
                $this->setFunctionHook( 'padright', array( 'CoreParserFunctions', 'padright' ), SFH_NO_HASH );
                $this->setFunctionHook( 'anchorencode', array( 'CoreParserFunctions', 'anchorencode' ), SFH_NO_HASH );
+               $this->setFunctionHook( 'special', array( 'CoreParserFunctions', 'special' ) );
+               $this->setFunctionHook( 'defaultsort', array( 'CoreParserFunctions', 'defaultsort' ), SFH_NO_HASH );
 
                if ( $wgAllowDisplayTitle ) {
                        $this->setFunctionHook( 'displaytitle', array( 'CoreParserFunctions', 'displaytitle' ), SFH_NO_HASH );
@@ -171,7 +181,6 @@ class Parser
                }
 
                $this->initialiseVariables();
-
                $this->mFirstCall = false;
                wfProfileOut( __METHOD__ );
        }
@@ -191,7 +200,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(
@@ -205,7 +214,7 @@ class Parser
                        'texts' => array(),
                        'titles' => array()
                );
-               $this->mRevisionId = null;
+               $this->mRevisionTimestamp = $this->mRevisionId = null;
 
                /**
                 * Prefix for temporary replacement strings for the multipass parser.
@@ -227,6 +236,7 @@ class Parser
                        'post-expand' => 0,
                        'arg' => 0
                );
+               $this->mDefaultSort = false;
 
                wfRunHooks( 'ParserClearState', array( &$this ) );
                wfProfileOut( __METHOD__ );
@@ -256,7 +266,6 @@ class Parser
         * Convert wikitext to HTML
         * Do not call this function recursively.
         *
-        * @private
         * @param string $text Text we want to parse
         * @param Title &$title A title object
         * @param array $options
@@ -265,7 +274,7 @@ class Parser
         * @param int $revid number to pass in {{REVISIONID}}
         * @return ParserOutput a ParserOutput
         */
-       function parse( $text, &$title, $options, $linestart = true, $clearState = true, $revid = null ) {
+       public function parse( $text, &$title, $options, $linestart = true, $clearState = true, $revid = null ) {
                /**
                 * First pass--just handle <nowiki> sections, pass the rest off
                 * to internalParse() which does all the real work.
@@ -273,6 +282,7 @@ class Parser
 
                global $wgUseTidy, $wgAlwaysUseTidy, $wgContLang;
                $fname = 'Parser::parse-' . wfGetCaller();
+               wfProfileIn( __METHOD__ );
                wfProfileIn( $fname );
 
                if ( $clearState ) {
@@ -282,22 +292,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->setOutputType( 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 ) );
-
+               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(
@@ -320,7 +325,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 ) );
 
@@ -370,7 +375,9 @@ class Parser
                }
                $this->mOutput->setText( $text );
                $this->mRevisionId = $oldRevisionId;
+               $this->mRevisionTimestamp = $oldRevisionTimestamp;
                wfProfileOut( $fname );
+               wfProfileOut( __METHOD__ );
 
                return $this->mOutput;
        }
@@ -381,10 +388,9 @@ class Parser
         */
        function recursiveTagParse( $text ) {
                wfProfileIn( __METHOD__ );
-               $x =& $this->mStripState;
-               wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$x ) );
-               $text = $this->strip( $text, $x );
-               wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$x ) );
+               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;
@@ -400,16 +406,14 @@ class Parser
                $this->setOutputType( OT_PREPROCESS );
                $this->mOptions = $options;
                $this->mTitle = $title;
-               $x =& $this->mStripState;
-               wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$x ) );
-               $text = $this->strip( $text, $x );
-               wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$x ) );
+               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->unstrip( $text, $x );
-               $text = $this->unstripNowiki( $text, $x );
+               $text = $this->mStripState->unstripBoth( $text );
                wfProfileOut( __METHOD__ );
                return $text;
        }
@@ -516,7 +520,8 @@ 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
@@ -528,12 +533,15 @@ class Parser
         *
         * @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);
 
                $uniq_prefix = $this->mUniqPrefix;
-               $commentState = array();
+               $commentState = new ReplacementArray;
+               $nowikiItems = array();
+               $generalItems = array();
 
                $elements = array_merge(
                        array( 'nowiki', 'gallery' ),
@@ -578,10 +586,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 );
@@ -600,18 +608,22 @@ class Parser
                                $output = $tag;
                        }
 
-                       // Unstrip the output, because unstrip() is no longer recursive so
-                       // it won't do it itself
-                       $output = $this->unstrip( $output, $state );
+                       // Unstrip the output, to support recursive strip() calls
+                       $output = $state->unstripBoth( $output );
 
                        if( !$stripcomments && $element == '!--' ) {
-                               $commentState[$marker] = $output;
+                               $commentState->setPair( $marker, $output );
                        } elseif ( $element == 'html' || $element == 'nowiki' ) {
-                               $state['nowiki'][$marker] = $output;
+                               $nowikiItems[$marker] = $output;
                        } else {
-                               $state['general'][$marker] = $output;
+                               $generalItems[$marker] = $output;
                        }
                }
+               # Add the new items to the state
+               # We do this after the loop instead of during it to avoid slowing 
+               # down the recursive unstrip
+               $state->nowiki->mergeArray( $nowikiItems );
+               $state->general->mergeArray( $generalItems );
 
                # Unstrip comments unless explicitly told otherwise.
                # (The comments are always stripped prior to this point, so as to
@@ -619,7 +631,7 @@ class Parser
                # a comment.)
                if ( !$stripcomments ) {
                        // Put them all back and forget them
-                       $text = strtr( $text, $commentState );
+                       $text = $commentState->replace( $text );
                }
 
                wfProfileOut( __METHOD__ );
@@ -631,35 +643,27 @@ class Parser
         *
         * always call unstripNoWiki() after this one
         * @private
+        * @deprecated use $this->mStripState->unstrip()
         */
        function unstrip( $text, $state ) {
-               if ( !isset( $state['general'] ) ) {
-                       return $text;
-               }
-
-               wfProfileIn( __METHOD__ );
-               # TODO: good candidate for FSS
-               $text = strtr( $text, $state['general'] );
-               wfProfileOut( __METHOD__ );
-               return $text;
+               return $state->unstripGeneral( $text );
        }
 
        /**
         * Always call this after unstrip() to preserve the order
         *
         * @private
+        * @deprecated use $this->mStripState->unstrip()
         */
        function unstripNoWiki( $text, $state ) {
-               if ( !isset( $state['nowiki'] ) ) {
-                       return $text;
-               }
-
-               wfProfileIn( __METHOD__ );
-               # TODO: good candidate for FSS
-               $text = strtr( $text, $state['nowiki'] );
-               wfProfileOut( __METHOD__ );
+               return $state->unstripNoWiki( $text );
+       }
 
-               return $text;
+       /**
+        * @deprecated use $this->mStripState->unstripBoth()
+        */
+       function unstripForHTML( $text ) {
+               return $this->mStripState->unstripBoth( $text );
        }
 
        /**
@@ -671,10 +675,7 @@ class Parser
         */
        function insertStripItem( $text, &$state ) {
                $rnd = $this->mUniqPrefix . '-item' . Parser::getRandomString();
-               if ( !$state ) {
-                       $state = array();
-               }
-               $state['general'][$rnd] = $text;
+               $state->general->setPair( $rnd, $text );
                return $rnd;
        }
 
@@ -726,7 +727,7 @@ class Parser
                $descriptorspec = array(
                        0 => array('pipe', 'r'),
                        1 => array('pipe', 'w'),
-                       2 => array('file', '/dev/null', 'a')
+                       2 => array('file', '/dev/null', 'a')  // FIXME: this line in UNIX-specific, it generates a warning on Windows, because /dev/null is not a valid Windows file.
                );
                $pipes = array();
                $process = proc_open("$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes);
@@ -791,141 +792,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 ) ;
+                       $line = trim ( $line );
+
+                       if( $line == '' ) { // empty line, go to next line
+                               continue;
+                       }
+                       $first_character = $line{0};
                        $matches = array();
-                       if ( preg_match( '/^(:*)\{\|(.*)$/', $x, $matches ) ) {
+
+                       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 );
                                        }
 
-                                       $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 = '' ;
+                                       $last_tag = array_pop ( $last_tag_history );
+
+                                       if ( array_pop ( $td_history ) ) {
+                                               $previous = "</{$last_tag}>{$previous}";
                                        }
-                                       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;
        }
 
        /**
@@ -941,8 +992,7 @@ class Parser
                wfProfileIn( $fname );
 
                # Hook to suspend the parser in this state
-               $x =& $this->mStripState; // FIXME: Please check that this initialization is correct.
-               if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$x ) ) ) {
+               if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$this->mStripState ) ) ) {
                        wfProfileOut( $fname );
                        return $text ;
                }
@@ -950,11 +1000,12 @@ class Parser
                # 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' ) );
 
                $text = $this->replaceVariables( $text, $args );
+               wfRunHooks( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) );
 
                // Tables need to come after variable replacement for things to work
                // properly; putting them before other transformations should keep
@@ -999,7 +1050,11 @@ class Parser
                            <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+([0-9Xx-]+)          # ISBN, capture number as m[2]
+                           ISBN\s+(\b                  # ISBN, capture number as m[2]
+                                     (?: 97[89] [\ \-]? )?   # optional 13-digit ISBN prefix
+                                     (?: [0-9]  [\ \-]? ){9} # 9 digits with opt. delimiters
+                                     [0-9Xx]                 # check digit
+                                   \b)
                        )!x', array( &$this, 'magicLinkCallback' ), $text );
                wfProfileOut( __METHOD__ );
                return $text;
@@ -1016,7 +1071,7 @@ class Parser
                                ' ' => '',
                                'x' => 'X',
                        ));
-                       $titleObj = Title::makeTitle( NS_SPECIAL, 'Booksources' );
+                       $titleObj = SpecialPage::getTitleFor( 'Booksources' );
                        $text = '<a href="' .
                                $titleObj->escapeLocalUrl( "isbn=$num" ) .
                                "\" class=\"internal\">ISBN $isbn</a>";
@@ -1035,7 +1090,7 @@ class Parser
                        }
 
                        $url = wfMsg( $urlmsg, $id);
-                       $sk =& $this->mOptions->getSkin();
+                       $sk = $this->mOptions->getSkin();
                        $la = $sk->getExternalLinkAttributes( $url, $keyword.$id );
                        $text = "<a href=\"{$url}\"{$la}>{$keyword} {$id}</a>";
                }
@@ -1236,7 +1291,8 @@ class Parser
                                $output .= '</i>';
                        if ($state == 'bi')
                                $output .= '</b>';
-                       if ($state == 'both')
+                       # There might be lonely ''''', so make sure we have a buffer
+                       if ($state == 'both' && $buffer)
                                $output .= '<b><i>'.$buffer.'</i></b>';
                        return $output;
                }
@@ -1255,7 +1311,7 @@ class Parser
                $fname = 'Parser::replaceExternalLinks';
                wfProfileIn( $fname );
 
-               $sk =& $this->mOptions->getSkin();
+               $sk = $this->mOptions->getSkin();
 
                $bits = preg_split( EXT_LINK_BRACKETED, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
 
@@ -1344,7 +1400,7 @@ class Parser
                $s = array_shift( $bits );
                $i = 0;
 
-               $sk =& $this->mOptions->getSkin();
+               $sk = $this->mOptions->getSkin();
 
                while ( $i < count( $bits ) ){
                        $protocol = $bits[$i++];
@@ -1417,7 +1473,7 @@ class Parser
         * @param string
         * @return string
         * @static
-        * @fixme This can merge genuinely required bits in the path or query string,
+        * @todo  This can merge genuinely required bits in the path or query string,
         *        breaking legit URLs. A proper fix would treat the various parts of
         *        the URL differently; as a workaround, just use the output for
         *        statistical records, not for actual linking/output.
@@ -1452,7 +1508,7 @@ class Parser
         * @private
         */
        function maybeMakeExternalImage( $url ) {
-               $sk =& $this->mOptions->getSkin();
+               $sk = $this->mOptions->getSkin();
                $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
                $imagesexception = !empty($imagesfrom);
                $text = false;
@@ -1482,7 +1538,7 @@ class Parser
                # the % is needed to support urlencoded titles as well
                if ( !$tc ) { $tc = Title::legalChars() . '#%'; }
 
-               $sk =& $this->mOptions->getSkin();
+               $sk = $this->mOptions->getSkin();
 
                #split the entire text string on occurences of [[
                $a = explode( '[[', ' ' . $s );
@@ -1501,7 +1557,6 @@ class Parser
                $e2 = wfMsgForContent( 'linkprefix' );
 
                $useLinkPrefixExtension = $wgContLang->linkPrefixExtension();
-
                if( is_null( $this->mTitle ) ) {
                        throw new MWException( __METHOD__.": \$this->mTitle is null\n" );
                }
@@ -1518,7 +1573,11 @@ class Parser
                        $prefix = '';
                }
 
-               $selflink = $this->mTitle->getPrefixedText();
+               if($wgContLang->hasVariants()) {
+                       $selflink = $wgContLang->convertLinkToAllVariants($this->mTitle->getPrefixedText());
+               } else {
+                       $selflink = array($this->mTitle->getPrefixedText());
+               }
                $useSubpages = $this->areSubpagesAllowed();
                wfProfileOut( $fname.'-setup' );
 
@@ -1572,7 +1631,7 @@ class Parser
                                $might_be_img = true;
                                $text = $m[2];
                                if ( strpos( $m[1], '%' ) !== false ) {
-                                      $m[1] = urldecode($m[1]);
+                                       $m[1] = urldecode($m[1]);
                                }
                                $trail = "";
                        } else { # Invalid form; output directly
@@ -1586,7 +1645,7 @@ class Parser
                        # Don't allow internal links to pages containing
                        # PROTO: where PROTO is a valid URL protocol; these
                        # should be external links.
-                       if (preg_match('/^(\b(?:' . wfUrlProtocols() . '))/', $m[1])) {
+                       if (preg_match('/^\b(?:' . wfUrlProtocols() . ')/', $m[1])) {
                                $s .= $prefix . '[[' . $line ;
                                continue;
                        }
@@ -1606,7 +1665,7 @@ class Parser
 
                        wfProfileOut( "$fname-misc" );
                        wfProfileIn( "$fname-title" );
-                       $nt = Title::newFromText( $this->unstripNoWiki($link, $this->mStripState) );
+                       $nt = Title::newFromText( $this->mStripState->unstripNoWiki($link) );
                        if( !$nt ) {
                                $s .= $prefix . '[[' . $line;
                                wfProfileOut( "$fname-title" );
@@ -1669,8 +1728,8 @@ class Parser
                                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;
+                                       $s = rtrim($s . $prefix);
+                                       $s .= trim($trail, "\n") == '' ? '': $prefix . $trail;
                                        wfProfileOut( "$fname-interwiki" );
                                        continue;
                                }
@@ -1704,11 +1763,7 @@ class Parser
                                        $s = rtrim($s . "\n"); # bug 87
 
                                        if ( $wasblank ) {
-                                               if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
-                                                       $sortkey = $this->mTitle->getText();
-                                               } else {
-                                                       $sortkey = $this->mTitle->getPrefixedText();
-                                               }
+                                               $sortkey = $this->getDefaultSort();
                                        } else {
                                                $sortkey = $text;
                                        }
@@ -1728,11 +1783,12 @@ class Parser
                                }
                        }
 
-                       if( ( $nt->getPrefixedText() === $selflink ) &&
-                           ( $nt->getFragment() === '' ) ) {
-                               # Self-links are handled specially; generally de-link and change to bold.
-                               $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail );
-                               continue;
+                       # Self-link checking
+                       if( $nt->getFragment() === '' ) {
+                               if( in_array( $nt->getPrefixedText(), $selflink, true ) ) {
+                                       $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail );
+                                       continue;
+                               }
                        }
 
                        # Special and Media are pseudo-namespaces; no pages actually exist in them
@@ -1812,7 +1868,7 @@ class Parser
         */
        function makeKnownLinkHolder( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
                list( $inside, $trail ) = Linker::splitTrail( $trail );
-               $sk =& $this->mOptions->getSkin();
+               $sk = $this->mOptions->getSkin();
                $link = $sk->makeKnownLinkObj( $nt, $text, $query, $inside, $prefix );
                return $this->armorLinks( $link ) . $trail;
        }
@@ -1830,7 +1886,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 );
        }
 
@@ -1873,9 +1929,10 @@ class Parser
                        # Look at the first character
                        if( $target != '' && $target{0} == '/' ) {
                                # / at end means we don't want the slash to be shown
-                               if( substr( $target, -1, 1 ) == '/' ) {
-                                       $target = substr( $target, 1, -1 );
-                                       $noslash = $target;
+                               $m = array();
+                               $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
+                               if( $trailingSlashes ) {
+                                       $noslash = $target = substr( $target, 1, -strlen($m[0][0]) );
                                } else {
                                        $noslash = substr( $target, 1 );
                                }
@@ -2084,10 +2141,10 @@ class Parser
                                wfProfileIn( "$fname-paragraph" );
                                # No prefix (not in list)--go to paragraph mode
                                // XXX: use a stack for nestable elements like span, table and div
-                               $openmatch = preg_match('/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/center|<\\/tr|<\\/td|<\\/th)/iS', $t );
+                               $openmatch = preg_match('/(?:<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/tr|<\\/td|<\\/th)/iS', $t );
                                $closematch = preg_match(
-                                       '/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'.
-                                       '<td|<th|<div|<\\/div|<hr|<\\/pre|<\\/p|'.$this->mUniqPrefix.'-pre|<\\/li|<\\/ul|<\\/ol|<center)/iS', $t );
+                                       '/(?:<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'.
+                                       '<td|<th|<\\/?div|<hr|<\\/pre|<\\/p|'.$this->mUniqPrefix.'-pre|<\\/li|<\\/ul|<\\/ol|<\\/?center)/iS', $t );
                                if ( $openmatch or $closematch ) {
                                        $paragraphStack = false;
                                        # TODO bug 5718: paragraph closed
@@ -2429,15 +2486,15 @@ class Parser
                        case 'revisionid':
                                return $this->mRevisionId;
                        case 'revisionday':
-                               return intval( substr( wfRevisionTimestamp( $this->mRevisionId ), 6, 2 ) );
+                               return intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
                        case 'revisionday2':
-                               return substr( wfRevisionTimestamp( $this->mRevisionId ), 6, 2 );
+                               return substr( $this->getRevisionTimestamp(), 6, 2 );
                        case 'revisionmonth':
-                               return intval( substr( wfRevisionTimestamp( $this->mRevisionId ), 4, 2 ) );
+                               return intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
                        case 'revisionyear':
-                               return substr( wfRevisionTimestamp( $this->mRevisionId ), 0, 4 );
+                               return substr( $this->getRevisionTimestamp(), 0, 4 );
                        case 'revisiontimestamp':
-                               return wfRevisionTimestamp( $this->mRevisionId );
+                               return $this->getRevisionTimestamp();
                        case 'namespace':
                                return str_replace('_',' ',$wgContLang->getNsText( $this->mTitle->getNamespace() ) );
                        case 'namespacee':
@@ -2479,15 +2536,17 @@ class Parser
                        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 'numberofedits':
+                               return $varCache[$index]  = $wgContLang->formatNum( SiteStats::edits() );
                        case 'currenttimestamp':
                                return $varCache[$index] = wfTimestampNow();
                        case 'localtimestamp':
@@ -2802,6 +2861,30 @@ class Parser
                return $text;
        }
 
+
+       /// Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
+       static function createAssocArgs( $args ) {
+               $assocArgs = array();
+               $index = 1;
+               foreach( $args as $arg ) {
+                       $eqpos = strpos( $arg, '=' );
+                       if ( $eqpos === false ) {
+                               $assocArgs[$index++] = $arg;
+                       } else {
+                               $name = trim( substr( $arg, 0, $eqpos ) );
+                               $value = trim( substr( $arg, $eqpos+1 ) );
+                               if ( $value === false ) {
+                                       $value = '';
+                               }
+                               if ( $name !== false ) {
+                                       $assocArgs[$name] = $value;
+                               }
+                       }
+               }
+
+               return $assocArgs;
+       }
+
        /**
         * Return the text of a template, after recursively
         * replacing any variables or templates within the template.
@@ -2814,7 +2897,7 @@ class Parser
         * @private
         */
        function braceSubstitution( $piece ) {
-               global $wgContLang, $wgLang, $wgAllowDisplayTitle;
+               global $wgContLang, $wgLang, $wgAllowDisplayTitle, $wgNonincludableNamespaces;
                $fname = __METHOD__ /*. '-L' . count( $this->mArgStack )*/;
                wfProfileIn( $fname );
                wfProfileIn( __METHOD__.'-setup' );
@@ -2825,6 +2908,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
 
@@ -2889,6 +2973,9 @@ class Parser
                }
                wfProfileOut( __METHOD__.'-modifiers' );
 
+               //save path level before recursing into functions & templates.
+               $lastPathLevel = $this->mTemplatePath;
+
                # Parser functions
                if ( !$found ) {
                        wfProfileIn( __METHOD__ . '-pfunc' );
@@ -2953,11 +3040,23 @@ class Parser
                        } else {
                                # set $text to cached message.
                                $text = $linestart . $this->mTemplates[$piece['title']];
+                               #treat title for cached page the same as others
+                               $ns = NS_TEMPLATE;
+                               $subpage = '';
+                               $part1 = $this->maybeDoSubpageLink( $part1, $subpage );
+                               if ($subpage !== '') {
+                                 $ns = $this->mTitle->getNamespace();
+                               }
+                               $title = Title::newFromText( $part1, $ns );
+                               //used by include size checking
+                               $titleText = $title->getPrefixedText();
+                               //used by edit section links
+                               $replaceHeadings = true;
+                               
                        }
                }
 
                # Load from database
-               $lastPathLevel = $this->mTemplatePath;
                if ( !$found ) {
                        wfProfileIn( __METHOD__ . '-loadtpl' );
                        $ns = NS_TEMPLATE;
@@ -2974,9 +3073,8 @@ class Parser
 
                        if ( !is_null( $title ) ) {
                                $titleText = $title->getPrefixedText();
-                               $checkVariantLink = sizeof($wgContLang->getVariants())>1;
                                # 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);
                                }
 
@@ -2990,8 +3088,11 @@ class Parser
                                                        $isHTML = true;
                                                        $this->disableCache();
                                                }
+                                       } else if ( $wgNonincludableNamespaces && in_array( $title->getNamespace(), $wgNonincludableNamespaces ) ) {
+                                               $found = false; //access denied
+                                               wfDebug( "$fname: template inclusion denied for " . $title->getPrefixedDBkey() );
                                        } else {
-                                               $articleContent = $this->fetchTemplate( $title );
+                                               list($articleContent,$title) = $this->fetchTemplateAndtitle( $title );
                                                if ( $articleContent !== false ) {
                                                        $found = true;
                                                        $text = $articleContent;
@@ -3049,24 +3150,7 @@ class Parser
                                $assocArgs = array();
                        } else {
                                # Clean up argument array
-                               $assocArgs = array();
-                               $index = 1;
-                               foreach( $args as $arg ) {
-                                       $eqpos = strpos( $arg, '=' );
-                                       if ( $eqpos === false ) {
-                                               $assocArgs[$index++] = $arg;
-                                       } else {
-                                               $name = trim( substr( $arg, 0, $eqpos ) );
-                                               $value = trim( substr( $arg, $eqpos+1 ) );
-                                               if ( $value === false ) {
-                                                       $value = '';
-                                               }
-                                               if ( $name !== false ) {
-                                                       $assocArgs[$name] = $value;
-                                               }
-                                       }
-                               }
-
+                               $assocArgs = self::createAssocArgs($args);
                                # Add a new element to the templace recursion path
                                $this->mTemplatePath[$part1] = 1;
                        }
@@ -3074,14 +3158,13 @@ class Parser
                        if ( !$noparse ) {
                                # If there are any <onlyinclude> tags, only include them
                                if ( in_string( '<onlyinclude>', $text ) && in_string( '</onlyinclude>', $text ) ) {
-                                       $m = array();
-                                       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->ot['html'] || $this->ot['pre'] ) {
@@ -3097,7 +3180,7 @@ class Parser
 
                                # 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 ) {
@@ -3139,7 +3222,8 @@ 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;
@@ -3175,45 +3259,52 @@ class Parser
        /**
         * Fetch the unparsed text of a template and register a reference to it.
         */
-       function fetchTemplate( $title ) {
+       function fetchTemplateAndtitle( $title ) {
                $text = false;
+               $finalTitle = $title;
                // Loop to fetch the article, with up to 1 redirect
                for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
                        $rev = Revision::newFromTitle( $title );
                        $this->mOutput->addTemplate( $title, $title->getArticleID() );
-                       if ( !$rev ) {
+                       if ( $rev ) {
+                               $text = $rev->getText();
+                       } elseif( $title->getNamespace() == NS_MEDIAWIKI ) {
+                               global $wgLang;
+                               $message = $wgLang->lcfirst( $title->getText() );
+                               $text = wfMsgForContentNoTrans( $message );
+                               if( wfEmptyMsg( $message, $text ) ) {
+                                       $text = false;
+                                       break;
+                               }
+                       } else {
                                break;
                        }
-                       $text = $rev->getText();
                        if ( $text === false ) {
                                break;
                        }
                        // Redirect?
+                       $finalTitle = $title;
                        $title = Title::newFromRedirect( $text );
                }
-               return $text;
+               return array($text,$finalTitle);
+       }
+
+       function fetchTemplate( $title ) {
+               $rv = $this->fetchTemplateAndtitle($title);
+               return $rv[0];
        }
 
        /**
         * Transclude an interwiki link.
         */
        function interwikiTransclude( $title, $action ) {
-               global $wgEnableScaryTranscluding, $wgCanonicalNamespaceNames;
+               global $wgEnableScaryTranscluding;
 
                if (!$wgEnableScaryTranscluding)
                        return wfMsg('scarytranscludedisabled');
 
-               // The namespace will actually only be 0 or 10, depending on whether there was a leading :
-               // But we'll handle it generally anyway
-               if ( $title->getNamespace() ) {
-                       // Use the canonical namespace, which should work anywhere
-                       $articleName = $wgCanonicalNamespaceNames[$title->getNamespace()] . ':' . $title->getDBkey();
-               } else {
-                       $articleName = $title->getDBkey();
-               }
+               $url = $title->getFullUrl( "action=$action" );
 
-               $url = str_replace('$1', urlencode($articleName), Title::getInterwikiLink($title->getInterwiki()));
-               $url .= "?action=$action";
                if (strlen($url) > 255)
                        return wfMsg('scarytranscludetoolong');
                return $this->fetchScaryTemplateMaybeFromCache($url);
@@ -3221,7 +3312,7 @@ class Parser
 
        function fetchScaryTemplateMaybeFromCache($url) {
                global $wgTranscludeCacheExpiry;
-               $dbr =& wfGetDB(DB_SLAVE);
+               $dbr = wfGetDB(DB_SLAVE);
                $obj = $dbr->selectRow('transcache', array('tc_time', 'tc_contents'),
                                array('tc_url' => $url));
                if ($obj) {
@@ -3236,7 +3327,7 @@ class Parser
                if (!$text)
                        return wfMsg('scarytranscludefailed', $url);
 
-               $dbw =& wfGetDB(DB_MASTER);
+               $dbw = wfGetDB(DB_MASTER);
                $dbw->replace('transcache', array('tc_url'), array(
                        'tc_url' => $url,
                        'tc_time' => time(),
@@ -3337,7 +3428,7 @@ class Parser
                global $wgMaxTocLevel, $wgContLang;
 
                $doNumberHeadings = $this->mOptions->getNumberHeadings();
-               if( !$this->mTitle->userCanEdit() ) {
+               if( !$this->mTitle->quickUserCan( 'edit' ) ) {
                        $showEditLink = 0;
                } else {
                        $showEditLink = $this->mOptions->getEditSection();
@@ -3352,7 +3443,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 );
+               $numMatches = preg_match_all( '/<H(?P<level>[1-6])(?P<attrib>.*?'.'>)(?P<header>.*?)<\/H[1-6] *>/i', $text, $matches );
 
                # if there are fewer than 4 headlines in the article, do not show TOC
                # unless it's been explicitly enabled.
@@ -3373,17 +3464,13 @@ class Parser
                        $enoughToc = true;
                }
 
-               # Never ever show TOC if no headers
-               if( $numMatches < 1 ) {
-                       $enoughToc = false;
-               }
-
                # We need this to perform operations on the HTML
-               $sk =& $this->mOptions->getSkin();
+               $sk = $this->mOptions->getSkin();
 
                # headline counter
                $headlineCount = 0;
                $sectionCount = 0; # headlineCount excluding template sections
+               $numVisible = 0;
 
                # Ugh .. the TOC should have neat indentation levels which can be
                # passed to the skin functions. These are determined here
@@ -3424,7 +3511,9 @@ class Parser
                                        $toclevel++;
                                        $sublevelCount[$toclevel] = 0;
                                        if( $toclevel<$wgMaxTocLevel ) {
+                                               $prevtoclevel = $toclevel;
                                                $toc .= $sk->tocIndent();
+                                               $numVisible++;
                                        }
                                }
                                elseif ( $level < $prevlevel && $toclevel > 1 ) {
@@ -3448,7 +3537,12 @@ class Parser
                                                }
                                        }
                                        if( $toclevel<$wgMaxTocLevel ) {
-                                               $toc .= $sk->tocUnindent( $prevtoclevel - $toclevel );
+                                               if($prevtoclevel < $wgMaxTocLevel) {
+                                                       # Unindent only if the previous toc level was shown :p
+                                                       $toc .= $sk->tocUnindent( $prevtoclevel - $toclevel );
+                                               } else {
+                                                       $toc .= $sk->tocLineEnd();
+                                               }
                                        }
                                }
                                else {
@@ -3476,8 +3570,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-->
@@ -3499,7 +3592,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)
@@ -3516,27 +3609,30 @@ class Parser
                        if( $enoughToc && ( !isset($wgMaxTocLevel) || $toclevel<$wgMaxTocLevel ) ) {
                                $toc .= $sk->tocLine($anchor, $tocline, $numbering, $toclevel);
                        }
+                       # give headline the correct <h#> tag
                        if( $showEditLink && ( !$istemplate || $templatetitle !== "" ) ) {
-                               if ( empty( $head[$headlineCount] ) ) {
-                                       $head[$headlineCount] = '';
-                               }
                                if( $istemplate )
-                                       $head[$headlineCount] .= $sk->editSectionLinkForOther($templatetitle, $templatesection);
+                                       $editlink = $sk->editSectionLinkForOther($templatetitle, $templatesection);
                                else
-                                       $head[$headlineCount] .= $sk->editSectionLink($this->mTitle, $sectionCount+1, $headline_hint);
+                                       $editlink = $sk->editSectionLink($this->mTitle, $sectionCount+1, $headline_hint);
+                       } else {
+                               $editlink = '';
                        }
-
-                       # give headline the correct <h#> tag
-                       @$head[$headlineCount] .= "<a name=\"$anchor\"></a><h".$level.$matches[2][$headlineCount] .$headline.'</h'.$level.'>';
+                       $head[$headlineCount] = $sk->makeHeadline( $level, $matches['attrib'][$headlineCount], $anchor, $headline, $editlink );
 
                        $headlineCount++;
                        if( !$istemplate )
                                $sectionCount++;
                }
 
+               # Never ever show TOC if no headers
+               if( $numVisible < 1 ) {
+                       $enoughToc = false;
+               }
+               
                if( $enoughToc ) {
-                       if( $toclevel<$wgMaxTocLevel ) {
-                               $toc .= $sk->tocUnindent( $toclevel - 1 );
+                       if( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
+                               $toc .= $sk->tocUnindent( $prevtoclevel - 1 );
                        }
                        $toc = $sk->tocList( $toc );
                }
@@ -3594,15 +3690,14 @@ class Parser
                        $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;
        }
 
@@ -3669,11 +3764,7 @@ class Parser
                }
 
                # Trim trailing whitespace
-               # __END__ tag allows for trailing
-               # whitespace to be deliberately included
                $text = rtrim( $text );
-               $mw =& MagicWord::get( 'end' );
-               $mw->matchAndRemove( $text );
 
                return $text;
        }
@@ -3793,7 +3884,7 @@ class Parser
 
                wfProfileIn($fname);
 
-               if ( $wgTitle ) {
+               if ( $wgTitle && !( $wgTitle instanceof FakeTitle ) ) {
                        $this->mTitle = $wgTitle;
                } else {
                        $this->mTitle = Title::newFromText('msg');
@@ -3825,7 +3916,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;
@@ -3856,7 +3947,7 @@ class Parser
         * @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
@@ -3905,7 +3996,6 @@ class Parser
         */
        function replaceLinkHolders( &$text, $options = 0 ) {
                global $wgUser;
-               global $wgOutputReplace;
                global $wgContLang;
 
                $fname = 'Parser::replaceLinkHolders';
@@ -3913,12 +4003,12 @@ class Parser
 
                $pdbks = array();
                $colours = array();
-               $sk =& $this->mOptions->getSkin();
+               $sk = $this->mOptions->getSkin();
                $linkCache =& LinkCache::singleton();
 
                if ( !empty( $this->mLinkHolders['namespaces'] ) ) {
                        wfProfileIn( $fname.'-check' );
-                       $dbr =& wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_SLAVE );
                        $page = $dbr->tableName( 'page' );
                        $threshold = $wgUser->getOption('stubthreshold');
 
@@ -3984,24 +4074,22 @@ class Parser
                                        $linkCache->addGoodLinkObj( $s->page_id, $title );
                                        $this->mOutput->addLink( $title, $s->page_id );
 
-                                       if ( $threshold >  0 ) {
-                                               $size = $s->page_len;
-                                               if ( $s->page_is_redirect || $s->page_namespace != 0 || $size >= $threshold ) {
-                                                       $colours[$pdbk] = 1;
-                                               } else {
-                                                       $colours[$pdbk] = 2;
-                                               }
-                                       } else {
-                                               $colours[$pdbk] = 1;
-                                       }
+                                       $colours[$pdbk] = ( $s->page_len >= $threshold || # always true if $threshold <= 0
+                                                           $s->page_is_redirect ||
+                                                           !Namespace::isContent( $s->page_namespace )
+                                                           ? 1 : 2 );
                                }
                        }
                        wfProfileOut( $fname.'-check' );
 
-                       # Do a second query for different language variants of links (if needed)
+                       # Do a second query for different language variants of links and categories
                        if($wgContLang->hasVariants()){
                                $linkBatch = new LinkBatch();
-                               $variantMap = array(); // maps $pdbkey_Variant => $pdbkey_original
+                               $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 ) {
@@ -4010,17 +4098,33 @@ class Parser
                                                continue;
 
                                        $pdbk = $title->getPrefixedDBkey();
+                                       $titleText = $title->getText();
 
                                        // generate all variants of the link title text
-                                       $allTextVariants = $wgContLang->convertLinkToAllVariants($title->getText());
+                                       $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){
-                                                       $variantTitle = Title::makeTitle( $ns, $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){
+                                               if($variant != $category){
+                                                       $variantTitle = Title::newFromDBkey( Title::makeName(NS_CATEGORY,$variant) );
                                                        if(is_null($variantTitle)) continue;
                                                        $linkBatch->addObj( $variantTitle );
-                                                       $variantMap[$variantTitle->getPrefixedDBkey()][] = $key;
+                                                       $categoryMap[$variant] = $category;
                                                }
                                        }
                                }
@@ -4047,10 +4151,14 @@ class Parser
 
                                                $variantTitle = Title::makeTitle( $s->page_namespace, $s->page_title );
                                                $varPdbk = $variantTitle->getPrefixedDBkey();
-                                               $linkCache->addGoodLinkObj( $s->page_id, $variantTitle );
-                                               $this->mOutput->addLink( $variantTitle, $s->page_id );
+                                               $vardbk = $variantTitle->getDBkey();
 
-                                               $holderKeys = $variantMap[$varPdbk];
+                                               $holderKeys = array();
+                                               if(isset($variantMap[$varPdbk])){
+                                                       $holderKeys = $variantMap[$varPdbk];
+                                                       $linkCache->addGoodLinkObj( $s->page_id, $variantTitle );
+                                                       $this->mOutput->addLink( $variantTitle, $s->page_id );
+                                               }
 
                                                // loop over link holders
                                                foreach($holderKeys as $key){
@@ -4079,13 +4187,33 @@ class Parser
                                                                }
                                                        }
                                                }
+
+                                               // 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-->";
@@ -4094,27 +4222,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' );
@@ -4125,15 +4253,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' );
                }
@@ -4186,11 +4315,11 @@ class 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>';
        }
 
@@ -4205,13 +4334,27 @@ class Parser
         */
        function renderImageGallery( $text, $params ) {
                $ig = new ImageGallery();
+               $ig->setContextTitle( $this->mTitle );
                $ig->setShowBytes( false );
                $ig->setShowFilename( false );
                $ig->setParsing();
                $ig->useSkin( $this->mOptions->getSkin() );
 
-               if( isset( $params['caption'] ) )
-                       $ig->setCaption( $params['caption'] );
+               if( isset( $params['caption'] ) ) {
+                       $caption = $params['caption'];
+                       $caption = htmlspecialchars( $caption );
+                       $caption = $this->replaceInternalLinks( $caption );
+                       $ig->setCaptionHtml( $caption );
+               }
+               if( isset( $params['perrow'] ) ) {
+                       $ig->setPerRow( $params['perrow'] );
+               }
+               if( isset( $params['widths'] ) ) {
+                       $ig->setWidths( $params['widths'] );
+               }
+               if( isset( $params['heights'] ) ) {
+                       $ig->setHeights( $params['heights'] );
+               }
 
                $lines = explode( "\n", $text );
                foreach ( $lines as $line ) {
@@ -4257,10 +4400,8 @@ class Parser
         * Parse image options text and use it to make an image
         */
        function makeImage( $nt, $options ) {
-               global $wgUseImageResize, $wgDjvuRenderer;
-
-               $align = '';
-
+               # @TODO: let the MediaHandler specify its transform parameters
+               #
                # Check if the options text is of the form "options|alt text"
                # Options are:
                #  * thumbnail          make a thumbnail with enlarge-icon and caption, alignment depends on lang
@@ -4270,61 +4411,74 @@ class Parser
                #  * ___px              scale to ___ pixels width, no aligning. e.g. use in taxobox
                #  * center             center the image
                #  * framed             Keep original image size, no magnify-button.
-
-               $part = explode( '|', $options);
-
+               # vertical-align values (no % or length right now):
+               #  * baseline
+               #  * sub
+               #  * super
+               #  * top
+               #  * text-top
+               #  * middle
+               #  * bottom
+               #  * text-bottom
+
+
+               $part = array_map( 'trim', explode( '|', $options) );
+
+               $mwAlign = array();
+               $alignments = array( 'left', 'right', 'center', 'none', 'baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom' );
+               foreach ( $alignments as $alignment ) {
+                       $mwAlign[$alignment] =& MagicWord::get( 'img_'.$alignment );
+               }
                $mwThumb  =& MagicWord::get( 'img_thumbnail' );
                $mwManualThumb =& MagicWord::get( 'img_manualthumb' );
-               $mwLeft   =& MagicWord::get( 'img_left' );
-               $mwRight  =& MagicWord::get( 'img_right' );
-               $mwNone   =& MagicWord::get( 'img_none' );
                $mwWidth  =& MagicWord::get( 'img_width' );
-               $mwCenter =& MagicWord::get( 'img_center' );
                $mwFramed =& MagicWord::get( 'img_framed' );
                $mwPage   =& MagicWord::get( 'img_page' );
                $caption = '';
 
-               $width = $height = $framed = $thumb = false;
-               $page = null;
+               $params = array();
+               $framed = $thumb = false;
                $manual_thumb = '' ;
+               $align = $valign = '';
+               $sk = $this->mOptions->getSkin();
 
                foreach( $part as $val ) {
-                       if ( $wgUseImageResize && ! is_null( $mwThumb->matchVariableStartToEnd($val) ) ) {
+                       if ( !is_null( $mwThumb->matchVariableStartToEnd($val) ) ) {
                                $thumb=true;
                        } elseif ( ! is_null( $match = $mwManualThumb->matchVariableStartToEnd($val) ) ) {
                                # use manually specified thumbnail
                                $thumb=true;
                                $manual_thumb = $match;
-                       } elseif ( ! is_null( $mwRight->matchVariableStartToEnd($val) ) ) {
-                               # remember to set an alignment, don't render immediately
-                               $align = 'right';
-                       } elseif ( ! is_null( $mwLeft->matchVariableStartToEnd($val) ) ) {
-                               # remember to set an alignment, don't render immediately
-                               $align = 'left';
-                       } elseif ( ! is_null( $mwCenter->matchVariableStartToEnd($val) ) ) {
-                               # remember to set an alignment, don't render immediately
-                               $align = 'center';
-                       } 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] );
+                       } else {
+                               foreach( $alignments as $alignment ) {
+                                       if ( ! is_null( $mwAlign[$alignment]->matchVariableStartToEnd($val) ) ) {
+                                               switch ( $alignment ) {
+                                                       case 'left': case 'right': case 'center': case 'none':
+                                                               $align = $alignment; break;
+                                                       default:
+                                                               $valign = $alignment;
+                                               }
+                                               continue 2;
+                                       }
+                               }
+                               if ( ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) {
+                                       # Select a page in a multipage document
+                                       $params['page'] = $match;
+                               } elseif ( !isset( $params['width'] ) && ! 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 ) ) {
+                                                $params['width'] = intval( $m[1] );
+                                                $params['height'] = intval( $m[2] );
+                                       } else {
+                                               $params['width'] = intval($match);
+                                       }
+                               } elseif ( ! is_null( $mwFramed->matchVariableStartToEnd($val) ) ) {
+                                       $framed=true;
                                } else {
-                                       $width = intval($match);
+                                       $caption = $val;
                                }
-                       } elseif ( ! is_null( $mwFramed->matchVariableStartToEnd($val) ) ) {
-                               $framed=true;
-                       } else {
-                               $caption = $val;
                        }
                }
                # Strip bad stuff out of the alt text
@@ -4333,12 +4487,11 @@ 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, $page );
+               return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $params, $framed, $thumb, $manual_thumb, $valign );
        }
 
        /**
@@ -4360,15 +4513,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;
-       }
        /**#@-*/
 
        /**#@+
@@ -4404,14 +4552,14 @@ 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->setOutputType( OT_WIKI );
 
-               $striptext = $this->strip( $text, $striparray, true );
+               $striptext = $this->strip( $text, $stripState, true );
 
                $this->setOutputType( $oldOutputType );
                $this->mOptions = $oldOptions;
@@ -4421,24 +4569,6 @@ class Parser
                $uniq = preg_quote( $this->uniqPrefix(), '/' );
                $comment = "(?:$uniq-!--.*?QINU)";
                $secs = preg_split(
-               /*
-                       "/
-                       ^(
-                       (?:$comment|<\/?noinclude>)* # Initial comments will be stripped
-                       (?:
-                               (=+) # Should this be limited to 6?
-                               .+?  # Section title...
-                               \\2  # Ending = count must match start
-                       |
-                               ^
-                               <h([1-6])\b.*?>
-                               .*?
-                               <\/h\\3\s*>
-                       )
-                       (?:$comment|<\/?noinclude>|\s+)* # Trailing whitespace ok
-                       )$
-                       /mix",
-               */
                        "/
                        (
                                ^
@@ -4462,7 +4592,8 @@ class Parser
                                // "Section 0" returns the content before any other section.
                                $rv = $secs[0];
                        } else {
-                               $rv = "";
+                               //track missing section, will replace if found.
+                               $rv = $newtext;
                        }
                } elseif( $mode == "replace" ) {
                        if( $section == 0 ) {
@@ -4517,10 +4648,10 @@ class Parser
                                }
                        }
                }
-               # reinsert stripped tags
-               $rv = $this->unstrip( $rv, $striparray );
-               $rv = $this->unstripNoWiki( $rv, $striparray );
-               $rv = trim( $rv );
+               if (is_string($rv))
+                       # reinsert stripped tags
+                       $rv = trim( $stripState->unstripBoth( $rv ) );
+
                return $rv;
        }
 
@@ -4533,396 +4664,124 @@ class Parser
         *
         * @param $text String: text to look in
         * @param $section Integer: section number
+        * @param $deftext: default to return if section is not found
         * @return string text of the requested section
         */
-       function getSection( $text, $section ) {
-               return $this->extractSections( $text, $section, "get" );
+       public function getSection( $text, $section, $deftext='' ) {
+               return $this->extractSections( $text, $section, "get", $deftext );
        }
 
-       function replaceSection( $oldtext, $section, $text ) {
+       public function replaceSection( $oldtext, $section, $text ) {
                return $this->extractSections( $oldtext, $section, "replace", $text );
        }
 
-}
-
-/**
- * @todo document
- * @package MediaWiki
- */
-class ParserOutput
-{
-       var $mText,             # The output text
-               $mLanguageLinks,    # List of the full text of language links, in the order they appear
-               $mCategories,       # Map of category names to sort keys
-               $mContainsOldMagic, # Boolean variable indicating if the input contained variables like {{CURRENTDAY}}
-               $mCacheTime,        # Time when this object was generated, or -1 for uncacheable. Used in ParserCache.
-               $mVersion,          # Compatibility check
-               $mTitleText,        # title text of the chosen language variant
-               $mLinks,            # 2-D map of NS/DBK to ID for the links in the document. ID=zero for broken.
-               $mTemplates,        # 2-D map of NS/DBK to ID for the template references. ID=zero for broken.
-               $mImages,           # DB keys of the images used, in the array key only
-               $mExternalLinks,    # External link URLs, in the key only
-               $mHTMLtitle,            # Display HTML title
-               $mSubtitle,                     # Additional subtitle
-               $mNewSection,           # Show a new section link?
-               $mNoGallery;            # No gallery on category page? (__NOGALLERY__)
-
-       function ParserOutput( $text = '', $languageLinks = array(), $categoryLinks = array(),
-               $containsOldMagic = false, $titletext = '' )
-       {
-               $this->mText = $text;
-               $this->mLanguageLinks = $languageLinks;
-               $this->mCategories = $categoryLinks;
-               $this->mContainsOldMagic = $containsOldMagic;
-               $this->mCacheTime = '';
-               $this->mVersion = MW_PARSER_VERSION;
-               $this->mTitleText = $titletext;
-               $this->mLinks = array();
-               $this->mTemplates = array();
-               $this->mImages = array();
-               $this->mExternalLinks = array();
-               $this->mHTMLtitle = "" ;
-               $this->mSubtitle = "" ;
-               $this->mNewSection = false;
-               $this->mNoGallery = false;
-       }
-
-       function getText()                   { return $this->mText; }
-       function &getLanguageLinks()          { return $this->mLanguageLinks; }
-       function getCategoryLinks()          { return array_keys( $this->mCategories ); }
-       function &getCategories()            { return $this->mCategories; }
-       function getCacheTime()              { return $this->mCacheTime; }
-       function getTitleText()              { return $this->mTitleText; }
-       function &getLinks()                 { return $this->mLinks; }
-       function &getTemplates()             { return $this->mTemplates; }
-       function &getImages()                { return $this->mImages; }
-       function &getExternalLinks()         { return $this->mExternalLinks; }
-       function getNoGallery()              { return $this->mNoGallery; }
-       function getSubtitle()               { return $this->mSubtitle; }
-
-       function containsOldMagic()          { return $this->mContainsOldMagic; }
-       function setText( $text )            { return wfSetVar( $this->mText, $text ); }
-       function setLanguageLinks( $ll )     { return wfSetVar( $this->mLanguageLinks, $ll ); }
-       function setCategoryLinks( $cl )     { return wfSetVar( $this->mCategories, $cl ); }
-       function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); }
-       function setCacheTime( $t )          { return wfSetVar( $this->mCacheTime, $t ); }
-       function setTitleText( $t )          { return wfSetVar($this->mTitleText, $t); }
-       function setSubtitle( $st )          { return wfSetVar( $this->mSubtitle, $st ); }
-
-       function addCategory( $c, $sort )    { $this->mCategories[$c] = $sort; }
-       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;
-       }
-       function getNewSection() {
-               return (bool)$this->mNewSection;
-       }
-
-       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;
-       }
-
-       function addTemplate( $title, $id ) {
-               $ns = $title->getNamespace();
-               $dbk = $title->getDBkey();
-               if ( !isset( $this->mTemplates[$ns] ) ) {
-                       $this->mTemplates[$ns] = array();
+       /**
+        * 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__ );
                }
-               $this->mTemplates[$ns][$dbk] = $id;
+               return $this->mRevisionTimestamp;
        }
 
        /**
-        * Return true if this cached output object predates the global or
-        * per-article cache invalidation timestamps, or if it comes from
-        * an incompatible older version.
+        * Mutator for $mDefaultSort
         *
-        * @param string $touched the affected article's last touched timestamp
-        * @return bool
-        * @public
+        * @param $sort New value
         */
-       function expired( $touched ) {
-               global $wgCacheEpoch;
-               return $this->getCacheTime() == -1 || // parser says it's uncacheable
-                      $this->getCacheTime() < $touched ||
-                      $this->getCacheTime() <= $wgCacheEpoch ||
-                      !isset( $this->mVersion ) ||
-                      version_compare( $this->mVersion, MW_PARSER_VERSION, "lt" );
-       }
-}
-
-/**
- * Set options of the Parser
- * @todo document
- * @package MediaWiki
- */
-class ParserOptions
-{
-       # All variables are private
-       private $mUseTeX;                    # Use texvc to expand <math> tags
-       private $mUseDynamicDates;           # Use DateFormatter to format dates
-       private $mInterwikiMagic;            # Interlanguage links are removed and returned in an array
-       private $mAllowExternalImages;       # Allow external images inline
-       private $mAllowExternalImagesFrom;   # If not, any exception?
-       private $mSkin;                      # Reference to the preferred skin
-       private $mDateFormat;                # Date format index
-       private $mEditSection;               # Create "edit section" links
-       private $mNumberHeadings;            # Automatically number headings
-       private $mAllowSpecialInclusion;     # Allow inclusion of special pages
-       private $mTidy;                      # Ask for tidy cleanup
-       private $mInterfaceMessage;          # Which lang to call for PLURAL and GRAMMAR
-       private $mMaxIncludeSize;            # Maximum size of template expansions, in bytes
-       private $mRemoveComments;            # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
-
-       private $mUser;                      # Stored user object, just used to initialise the skin
-
-       function getUseTeX()                        { return $this->mUseTeX; }
-       function getUseDynamicDates()               { return $this->mUseDynamicDates; }
-       function getInterwikiMagic()                { return $this->mInterwikiMagic; }
-       function getAllowExternalImages()           { return $this->mAllowExternalImages; }
-       function getAllowExternalImagesFrom()       { return $this->mAllowExternalImagesFrom; }
-       function getEditSection()                   { return $this->mEditSection; }
-       function getNumberHeadings()                { return $this->mNumberHeadings; }
-       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 ) ) {
-                       $this->mSkin = $this->mUser->getSkin();
-               }
-               return $this->mSkin;
-       }
-
-       function getDateFormat() {
-               if ( !isset( $this->mDateFormat ) ) {
-                       $this->mDateFormat = $this->mUser->getDatePreference();
-               }
-               return $this->mDateFormat;
-       }
-
-       function setUseTeX( $x )                    { return wfSetVar( $this->mUseTeX, $x ); }
-       function setUseDynamicDates( $x )           { return wfSetVar( $this->mUseDynamicDates, $x ); }
-       function setInterwikiMagic( $x )            { return wfSetVar( $this->mInterwikiMagic, $x ); }
-       function setAllowExternalImages( $x )       { return wfSetVar( $this->mAllowExternalImages, $x ); }
-       function setAllowExternalImagesFrom( $x )   { return wfSetVar( $this->mAllowExternalImagesFrom, $x ); }
-       function setDateFormat( $x )                { return wfSetVar( $this->mDateFormat, $x ); }
-       function setEditSection( $x )               { return wfSetVar( $this->mEditSection, $x ); }
-       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 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 );
+       public function setDefaultSort( $sort ) {
+               $this->mDefaultSort = $sort;
        }
 
        /**
-        * Get parser options
-        * @static
+        * Accessor for $mDefaultSort
+        * Will use the title/prefixed title if none is set
+        *
+        * @return string
         */
-       static function newFromUser( $user ) {
-               return new ParserOptions( $user );
-       }
-
-       /** Get user options */
-       function initialiseFromUser( $userInput ) {
-               global $wgUseTeX, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages;
-               global $wgAllowExternalImagesFrom, $wgAllowSpecialInclusion, $wgMaxArticleSize;
-               $fname = 'ParserOptions::initialiseFromUser';
-               wfProfileIn( $fname );
-               if ( !$userInput ) {
-                       global $wgUser;
-                       if ( isset( $wgUser ) ) {
-                               $user = $wgUser;
-                       } else {
-                               $user = new User;
-                       }
+       public function getDefaultSort() {
+               if( $this->mDefaultSort !== false ) {
+                       return $this->mDefaultSort;
                } else {
-                       $user =& $userInput;
+                       return $this->mTitle->getNamespace() == NS_CATEGORY
+                                       ? $this->mTitle->getText()
+                                       : $this->mTitle->getPrefixedText();
                }
-
-               $this->mUser = $user;
-
-               $this->mUseTeX = $wgUseTeX;
-               $this->mUseDynamicDates = $wgUseDynamicDates;
-               $this->mInterwikiMagic = $wgInterwikiMagic;
-               $this->mAllowExternalImages = $wgAllowExternalImages;
-               $this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom;
-               $this->mSkin = null; # Deferred
-               $this->mDateFormat = null; # Deferred
-               $this->mEditSection = true;
-               $this->mNumberHeadings = $user->getOption( 'numberheadings' );
-               $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]];
 }
 
 /**
- * Return the total number of articles
+ * @todo document, briefly.
+ * @addtogroup Parser
  */
-function wfNumberOfArticles() {
-       global $wgNumberOfArticles;
-
-       wfLoadSiteStats();
-       return $wgNumberOfArticles;
-}
+class OnlyIncludeReplacer {
+       var $output = '';
 
-/**
- * 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;
-}
-
-/**
- * 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;
+       function replace( $matches ) { 
+               if ( substr( $matches[1], -1 ) == "\n" ) {
+                       $this->output .= substr( $matches[1], 0, -1 );
+               } else {
+                       $this->output .= $matches[1];
+               }
+       }
 }
 
 /**
- * Return the total number of pages
- * @return integer
+ * @todo document, briefly.
+ * @addtogroup Parser
  */
-function wfNumberOfPages() {
-       wfProfileIn( 'wfNumberOfPages' );
-       $dbr =& wfGetDB( DB_SLAVE );
-       $count = $dbr->selectField( 'site_stats', 'ss_total_pages', array(), 'wfNumberOfPages' );
-       wfProfileOut( 'wfNumberOfPages' );
-       return (int)$count;
-}
+class StripState {
+       var $general, $nowiki;
 
-/**
- * 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;
        }
-}
-
-/**
- * Get revision timestamp from the database considering timecorrection
- *
- * @param $id Int: page revision id
- * @return integer
- */
-function wfRevisionTimestamp( $id ) {
-       global $wgContLang;
-       $fname = 'wfRevisionTimestamp';
-
-       wfProfileIn( $fname );
-       $dbr =& wfGetDB( DB_SLAVE );
-       $timestamp = $dbr->selectField( 'revision', 'rev_timestamp',
-                       array( 'rev_id' => $id ), __METHOD__ );
-       $timestamp = $wgContLang->userAdjust( $timestamp );
-       wfProfileOut( $fname );
-
-       return $timestamp;
-}
 
-/**
- * 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;
+       }
 }
 
 ?>