Forbid '<', '>', ' ', '\n', '\r' in parser hook names.
[lhc/web/wiklou.git] / includes / parser / Parser.php
index 5e8a5c2..3d37703 100644 (file)
  *     produces altered wiki markup.
  * preprocess()
  *     removes HTML comments and expands templates
- * cleanSig()
+ * cleanSig() / cleanSigInSig()
  *     Cleans a signature before saving it to preferences
- * extractSections()
- *     Extracts sections from an article for section editing
+ * getSection()
+ *     Return the content of a section from an article for section editing
+ * replaceSection()
+ *     Replaces a section by number inside an article
  * getPreloadText()
  *     Removes <noinclude> sections, and <includeonly> tags.
  *
@@ -89,10 +91,20 @@ class Parser {
        const MARKER_SUFFIX = "-QINU\x7f";
 
        # Persistent:
-       var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables;
-       var $mSubstWords, $mImageParams, $mImageParamsMagicArray, $mStripList, $mMarkerIndex;
-       var $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols, $mDefaultStripList;
-       var $mVarCache, $mConf, $mFunctionTagHooks;
+       var $mTagHooks = array();
+       var $mTransparentTagHooks = array();
+       var $mFunctionHooks = array();
+       var $mFunctionSynonyms = array( 0 => array(), 1 => array() );
+       var $mFunctionTagHooks = array();
+       var $mStripList  = array();
+       var $mDefaultStripList  = array();
+       var $mVarCache = array();
+       var $mImageParams = array();
+       var $mImageParamsMagicArray = array();
+       var $mMarkerIndex = 0;
+       var $mFirstCall = true;
+       var $mVariables, $mSubstWords; # Initialised by initialiseVariables()
+       var $mConf, $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols; # Initialised in constructor
 
 
        # Cleared with clearState():
@@ -103,6 +115,7 @@ class Parser {
        var $mTplExpandCache; # empty-frame expansion cache
        var $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores;
        var $mExpensiveFunctionCount; # number of expensive parser function calls
+       var $mUser; # User object; only used when doing pre-save transform
 
        # Temporary
        # These are variables reset at least once per parse regardless of $clearState
@@ -110,8 +123,10 @@ class Parser {
        var $mTitle;        # Title context, used for self-link rendering and similar things
        var $mOutputType;   # Output type, one of the OT_xxx constants
        var $ot;            # Shortcut alias, see setOutputType()
+       var $mRevisionObject; # The revision object of the specified revision ID
        var $mRevisionId;   # ID to display in {{REVISIONID}} tags
        var $mRevisionTimestamp; # The timestamp of the specified revision ID
+       var $mRevisionUser; # Userto display in {{REVISIONUSER}} tag
        var $mRevIdForTs;   # The revision ID which was used to fetch the timestamp
 
        /**
@@ -121,16 +136,9 @@ class Parser {
         */
        function __construct( $conf = array() ) {
                $this->mConf = $conf;
-               $this->mTagHooks = array();
-               $this->mTransparentTagHooks = array();
-               $this->mFunctionHooks = array();
-               $this->mFunctionTagHooks = array();
-               $this->mFunctionSynonyms = array( 0 => array(), 1 => array() );
-               $this->mDefaultStripList = $this->mStripList = array();
                $this->mUrlProtocols = wfUrlProtocols();
                $this->mExtLinkBracketedRegex = '/\[(\b(' . wfUrlProtocols() . ')'.
                        '[^][<>"\\x00-\\x20\\x7F]+) *([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/S';
-               $this->mVarCache = array();
                if ( isset( $conf['preprocessorClass'] ) ) {
                        $this->mPreprocessorClass = $conf['preprocessorClass'];
                } elseif ( extension_loaded( 'domxml' ) ) {
@@ -142,8 +150,6 @@ class Parser {
                } else {
                        $this->mPreprocessorClass = 'Preprocessor_Hash';
                }
-               $this->mMarkerIndex = 0;
-               $this->mFirstCall = true;
        }
 
        /**
@@ -188,6 +194,7 @@ class Parser {
                        $this->firstCallInit();
                }
                $this->mOutput = new ParserOutput;
+               $this->mOptions->registerWatcher( array( $this->mOutput, 'recordOption' ) );
                $this->mAutonumber = 0;
                $this->mLastSection = '';
                $this->mDTopen = false;
@@ -197,8 +204,10 @@ class Parser {
                $this->mInPre = false;
                $this->mLinkHolders = new LinkHolderArray( $this );
                $this->mLinkID = 0;
-               $this->mRevisionTimestamp = $this->mRevisionId = null;
+               $this->mRevisionObject = $this->mRevisionTimestamp =
+                       $this->mRevisionId = $this->mRevisionUser = null;
                $this->mVarCache = array();
+               $this->mUser = null;
 
                /**
                 * Prefix for temporary replacement strings for the multipass parser.
@@ -262,19 +271,22 @@ class Parser {
                wfProfileIn( __METHOD__ );
                wfProfileIn( $fname );
 
+               $this->mOptions = $options;
                if ( $clearState ) {
                        $this->clearState();
                }
 
-               $options->resetUsage();
-               $this->mOptions = $options;
                $this->setTitle( $title ); # Page title has to be set for the pre-processor
 
                $oldRevisionId = $this->mRevisionId;
+               $oldRevisionObject = $this->mRevisionObject;
                $oldRevisionTimestamp = $this->mRevisionTimestamp;
+               $oldRevisionUser = $this->mRevisionUser;
                if ( $revid !== null ) {
                        $this->mRevisionId = $revid;
+                       $this->mRevisionObject = null;
                        $this->mRevisionTimestamp = null;
+                       $this->mRevisionUser = null;
                }
                $this->setOutputType( self::OT_HTML );
                wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
@@ -317,24 +329,17 @@ class Parser {
                }
 
                /**
-                * A page get its title converted except:
-                * a) Language conversion is globally disabled
-                * b) Title convert is globally disabled
-                * c) The page is a redirect page
-                * d) User request with a "linkconvert" set to "no"
-                * e) A "nocontentconvert" magic word has been set
-                * f) A "notitleconvert" magic word has been set
-                * g) User sets "noconvertlink" in his/her preference
-                *
-                * Note that if a user tries to set a title in a conversion
-                * rule but content conversion was not done, then the parser
-                * won't pick it up.  This is probably expected behavior.
+                * A converted title will be provided in the output object if title and
+                * content conversion are enabled, the article text does not contain 
+                * a conversion-suppressing double-underscore tag, and no 
+                * {{DISPLAYTITLE:...}} is present. DISPLAYTITLE takes precedence over
+                * automatic link conversion.
                 */
                if ( !( $wgDisableLangConversion
                                || $wgDisableTitleConversion
                                || isset( $this->mDoubleUnderscores['nocontentconvert'] )
                                || isset( $this->mDoubleUnderscores['notitleconvert'] )
-                               || $this->mOutput->getDisplayTitle() !== false ) ) 
+                               || $this->mOutput->getDisplayTitle() !== false ) )
                {
                        $convruletitle = $wgContLang->getConvRuleTitle();
                        if ( $convruletitle ) {
@@ -354,7 +359,7 @@ class Parser {
                $uniq_prefix = $this->mUniqPrefix;
                $matches = array();
                $elements = array_keys( $this->mTransparentTagHooks );
-               $text = self::extractTagsAndParams( $elements, $text, $matches, $uniq_prefix );
+               $text = $this->extractTagsAndParams( $elements, $text, $matches, $uniq_prefix );
 
                foreach ( $matches as $marker => $data ) {
                        list( $element, $content, $params, $tag ) = $data;
@@ -422,7 +427,9 @@ class Parser {
                $this->mOutput->setText( $text );
 
                $this->mRevisionId = $oldRevisionId;
+               $this->mRevisionObject = $oldRevisionObject;
                $this->mRevisionTimestamp = $oldRevisionTimestamp;
+               $this->mRevisionUser = $oldRevisionUser;
                wfProfileOut( $fname );
                wfProfileOut( __METHOD__ );
 
@@ -453,10 +460,9 @@ class Parser {
         */
        function preprocess( $text, $title, $options, $revid = null ) {
                wfProfileIn( __METHOD__ );
+               $this->mOptions = $options;
                $this->clearState();
                $this->setOutputType( self::OT_PREPROCESS );
-               $options->resetUsage();
-               $this->mOptions = $options;
                $this->setTitle( $title );
                if ( $revid !== null ) {
                        $this->mRevisionId = $revid;
@@ -477,10 +483,9 @@ class Parser {
         */
        public function getPreloadText( $text, $title, $options ) {
                # Parser (re)initialisation
+               $this->mOptions = $options;
                $this->clearState();
                $this->setOutputType( self::OT_PLAIN );
-               $options->resetUsage();
-               $this->mOptions = $options;
                $this->setTitle( $title );
 
                $flags = PPFrame::NO_ARGS | PPFrame::NO_TEMPLATES;
@@ -494,10 +499,20 @@ class Parser {
         * @private
         * @static
         */
-       static function getRandomString() {
+       static private function getRandomString() {
                return dechex( mt_rand( 0, 0x7fffffff ) ) . dechex( mt_rand( 0, 0x7fffffff ) );
        }
 
+       /**
+        * Set the current user.
+        * Should only be used when doing pre-save transform.
+        *
+        * @param $user Mixed: User object or null (to reset)
+        */
+       function setUser( $user ) {
+               $this->mUser = $user;
+       }
+
        /**
         * Accessor for mUniqPrefix.
         *
@@ -520,9 +535,9 @@ class Parser {
         * Set the context title
         */
        function setTitle( $t ) {
-               if ( !$t || $t instanceof FakeTitle ) {
-                       $t = Title::newFromText( 'NO TITLE' );
-               }
+               if ( !$t || $t instanceof FakeTitle ) {
+                       $t = Title::newFromText( 'NO TITLE' );
+               }
 
                if ( strval( $t->getFragment() ) !== '' ) {
                        # Strip the fragment to avoid various odd effects
@@ -538,7 +553,7 @@ class Parser {
         *
         * @return Title object
         */
-       function &getTitle() {
+       function getTitle() {
                return $this->mTitle;
        }
 
@@ -621,6 +636,19 @@ class Parser {
                }
        }
 
+       /**
+        * Get a User object either from $this->mUser, if set, or from the
+        * ParserOptions object otherwise
+        *
+        * @return User object
+        */
+       function getUser() {
+               if ( !is_null( $this->mUser ) ) {
+                       return $this->mUser;
+               }
+               return $this->mOptions->getUser();
+       }
+
        /**
         * Get a preprocessor object
         *
@@ -647,7 +675,7 @@ class Parser {
         *
         * @param $elements list of element names. Comments are always extracted.
         * @param $text Source text string.
-        * @param $matches Out parameter, Array: extarcted tags
+        * @param $matches Out parameter, Array: extracted tags
         * @param $uniq_prefix
         * @return String: stripped text
         *
@@ -787,9 +815,8 @@ class Parser {
         */
        function doTableStuff( $text ) {
                wfProfileIn( __METHOD__ );
-               
+
                $lines = StringUtils::explode( "\n", $text );
-               $text = null;
                $out = '';
                $td_history = array(); # Is currently a td tag open?
                $last_tag_history = array(); # Save history of last lag activated (td, th or caption)
@@ -798,22 +825,10 @@ class Parser {
                $has_opened_tr = array(); # Did this table open a <tr> element?
                $indent_level = 0; # indent level of the table
 
-               $table_tag = 'table';
-               $tr_tag = 'tr';
-               $th_tag = 'th';
-               $td_tag = 'td';
-               $caption_tag = 'caption';
-
-               $extra_table_attribs = null;
-               $extra_tr_attribs = null;
-               $extra_td_attribs = null;
-
-               $convert_style = false;
-
                foreach ( $lines as $outLine ) {
                        $line = trim( $outLine );
 
-                       if ( $line === '' ) { # empty line, go to next line                     
+                       if ( $line === '' ) { # empty line, go to next line
                                $out .= $outLine."\n";
                                continue;
                        }
@@ -826,31 +841,9 @@ class Parser {
                                $indent_level = strlen( $matches[1] );
 
                                $attributes = $this->mStripState->unstripBoth( $matches[2] );
+                               $attributes = Sanitizer::fixTagAttributes( $attributes , 'table' );
 
-                               $attr = Sanitizer::decodeTagAttributes( $attributes );
-
-                               $mode = @$attr['mode'];
-                               if ( !$mode ) $mode = 'data';
-
-                               if ( $mode == 'grid' || $mode == 'layout' ) {
-                                       $table_tag = 'div';
-                                       $tr_tag = 'div';
-                                       $th_tag = 'div';
-                                       $td_tag = 'div';
-                                       $caption_tag = 'div';
-
-                                       $extra_table_attribs = array( 'class' => 'grid-table' );
-                                       $extra_tr_attribs = array( 'class' => 'grid-row' );
-                                       $extra_td_attribs = array( 'class' => 'grid-cell' );
-
-                                       $convert_style = true;
-                               } 
-
-                               if ($convert_style) $attr['style'] = Sanitizer::styleFromAttributes( $attr );
-                               $attr = Sanitizer::validateTagAttributes( $attr, $table_tag );
-                               $attributes = Sanitizer::collapseTagAttributes( $attr, $extra_table_attribs );
-
-                               $outLine = str_repeat( '<dl><dd>' , $indent_level ) . "<$table_tag{$attributes}>";
+                               $outLine = str_repeat( '<dl><dd>' , $indent_level ) . "<table{$attributes}>";
                                array_push( $td_history , false );
                                array_push( $last_tag_history , '' );
                                array_push( $tr_history , false );
@@ -862,15 +855,15 @@ class Parser {
                                continue;
                        } elseif ( substr( $line , 0 , 2 ) === '|}' ) {
                                # We are ending a table
-                               $line = "</$table_tag>" . substr( $line , 2 );
+                               $line = '</table>' . substr( $line , 2 );
                                $last_tag = array_pop( $last_tag_history );
 
                                if ( !array_pop( $has_opened_tr ) ) {
-                                       $line = "<$tr_tag><$td_tag></$td_tag></$tr_tag>{$line}";
+                                       $line = "<tr><td></td></tr>{$line}";
                                }
 
                                if ( array_pop( $tr_history ) ) {
-                                       $line = "</$tr_tag>{$line}";
+                                       $line = "</tr>{$line}";
                                }
 
                                if ( array_pop( $td_history ) ) {
@@ -884,12 +877,7 @@ class Parser {
 
                                # Whats after the tag is now only attributes
                                $attributes = $this->mStripState->unstripBoth( $line );
-
-                               $attr = Sanitizer::decodeTagAttributes( $attributes );
-                               if ($convert_style) $attr['style'] = Sanitizer::styleFromAttributes( $attr );
-                               $attr = Sanitizer::validateTagAttributes( $attr, $tr_tag );
-                               $attributes = Sanitizer::collapseTagAttributes( $attr, $extra_tr_attribs );
-
+                               $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
                                array_pop( $tr_attributes );
                                array_push( $tr_attributes, $attributes );
 
@@ -899,7 +887,7 @@ class Parser {
                                array_push( $has_opened_tr , true );
 
                                if ( array_pop( $tr_history ) ) {
-                                       $line = "</$tr_tag>";
+                                       $line = '</tr>';
                                }
 
                                if ( array_pop( $td_history ) ) {
@@ -937,7 +925,7 @@ class Parser {
                                        if ( $first_character !== '+' ) {
                                                $tr_after = array_pop( $tr_attributes );
                                                if ( !array_pop( $tr_history ) ) {
-                                                       $previous = "<$tr_tag{$tr_after}>\n";
+                                                       $previous = "<tr{$tr_after}>\n";
                                                }
                                                array_push( $tr_history , true );
                                                array_push( $tr_attributes , '' );
@@ -952,11 +940,11 @@ class Parser {
                                        }
 
                                        if ( $first_character === '|' ) {
-                                               $last_tag = $td_tag;
+                                               $last_tag = 'td';
                                        } elseif ( $first_character === '!' ) {
-                                               $last_tag = $th_tag;
+                                               $last_tag = 'th';
                                        } elseif ( $first_character === '+' ) {
-                                               $last_tag = $caption_tag;
+                                               $last_tag = 'caption';
                                        } else {
                                                $last_tag = '';
                                        }
@@ -966,24 +954,15 @@ class Parser {
                                        # A cell could contain both parameters and data
                                        $cell_data = explode( '|' , $cell , 2 );
 
-                                       $attributes = '';
-
                                        # Bug 553: Note that a '|' inside an invalid link should not
                                        # be mistaken as delimiting cell parameters
                                        if ( strpos( $cell_data[0], '[[' ) !== false ) {
-                                               if ($extra_td_attribs) $attributes = Sanitizer::collapseTagAttributes( $extra_td_attribs );
-                                               $cell = "{$previous}<{$last_tag}{$attributes}>{$cell}";
+                                               $cell = "{$previous}<{$last_tag}>{$cell}";
                                        } elseif ( count( $cell_data ) == 1 ) {
-                                               if ($extra_td_attribs) $attributes = Sanitizer::collapseTagAttributes( $extra_td_attribs );
-                                               $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[0]}";
+                                               $cell = "{$previous}<{$last_tag}>{$cell_data[0]}";
                                        } else {
                                                $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
-
-                                               $attr = Sanitizer::decodeTagAttributes( $attributes );
-                                               if ($convert_style) $attr['style'] = Sanitizer::styleFromAttributes( $attr );
-                                               $attr = Sanitizer::validateTagAttributes( $attr, $last_tag );
-                                               $attributes = Sanitizer::collapseTagAttributes( $attr, $extra_td_attribs );
-
+                                               $attributes = Sanitizer::fixTagAttributes( $attributes , $last_tag );
                                                $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
                                        }
 
@@ -997,16 +976,16 @@ class Parser {
                # Closing open td, tr && table
                while ( count( $td_history ) > 0 ) {
                        if ( array_pop( $td_history ) ) {
-                               $out .= "</$td_tag>\n";
+                               $out .= "</td>\n";
                        }
                        if ( array_pop( $tr_history ) ) {
-                               $out .= "</$tr_tag>\n";
+                               $out .= "</tr>\n";
                        }
                        if ( !array_pop( $has_opened_tr ) ) {
-                               $out .= "<$tr_tag><$td_tag></$td_tag></$tr_tag>\n" ;
+                               $out .= "<tr><td></td></tr>\n" ;
                        }
 
-                       $out .= "</$table_tag>\n";
+                       $out .= "</table>\n";
                }
 
                # Remove trailing line-ending (b/c)
@@ -1015,7 +994,7 @@ class Parser {
                }
 
                # special case: don't return empty table
-               if ( $out === "<$table_tag>\n<$tr_tag><$td_tag></$td_tag></$tr_tag>\n</$table_tag>" ) {
+               if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
                        $out = '';
                }
 
@@ -1108,10 +1087,10 @@ class Parser {
                                (\\b(?:$prots)$urlChar+) |  # m[3]: Free external links" . '
                                (?:RFC|PMID)\s+([0-9]+) |   # m[4]: RFC or PMID, capture number
                                ISBN\s+(\b                  # m[5]: ISBN, capture number
-                                   (?: 97[89] [\ \-]? )?   # optional 13-digit ISBN prefix
-                                   (?: [0-9]  [\ \-]? ){9} # 9 digits with opt. delimiters
-                                   [0-9Xx]                 # check digit
-                                   \b)
+                                       (?: 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;
@@ -1129,7 +1108,6 @@ class Parser {
                        return $this->makeFreeExternalLink( $m[0] );
                } elseif ( isset( $m[4] ) && $m[4] !== '' ) {
                        # RFC or PMID
-                       $CssClass = '';
                        if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
                                $keyword = 'RFC';
                                $urlmsg = 'rfcurl';
@@ -1261,10 +1239,9 @@ class Parser {
                        # First, do some preliminary work. This may shift some apostrophes from
                        # being mark-up to being text. It also counts the number of occurrences
                        # of bold and italics mark-ups.
-                       $i = 0;
                        $numbold = 0;
                        $numitalics = 0;
-                       foreach ( $arr as $r ) {
+                       for ( $i = 0; $i < count( $arr ); $i++ ) {
                                if ( ( $i % 2 ) == 1 ) {
                                        # If there are ever four apostrophes, assume the first is supposed to
                                        # be text, and the remaining three constitute mark-up for bold text.
@@ -1288,7 +1265,6 @@ class Parser {
                                                $numbold++;
                                        }
                                }
-                               $i++;
                        }
 
                        # If there is an odd number of both bold and italics, it is likely
@@ -1414,7 +1390,7 @@ class Parser {
        /**
         * Replace external links (REL)
         *
-        * Note: this is all very hackish and the order of execution matters a lot.
+        * Note: this is all very hackish and the order of execution matters a lot.
         * Make sure to run maintenance/parserTests.php if you change this code.
         *
         * @private
@@ -1591,7 +1567,7 @@ class Parser {
                        $imagematch = false;
                }
                if ( $this->mOptions->getAllowExternalImages()
-                    || ( $imagesexception && $imagematch ) ) {
+                        || ( $imagesexception && $imagematch ) ) {
                        if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
                                # Image found
                                $text = $sk->makeExternalImage( $url );
@@ -1617,7 +1593,7 @@ class Parser {
 
        /**
         * Process [[ ]] wikilinks
-        * @return processed text
+        * @return String: processed text
         *
         * @private
         */
@@ -1651,7 +1627,7 @@ class Parser {
                $sk = $this->mOptions->getSkin( $this->mTitle );
                $holders = new LinkHolderArray( $this );
 
-               # split the entire text string on occurences of [[
+               # split the entire text string on occurences of [[
                $a = StringUtils::explode( '[[', ' ' . $s );
                # get the first element (all text up to first [[), and remove the space we added
                $s = $a->current();
@@ -1743,14 +1719,14 @@ class Parser {
                                # fix up urlencoded title texts
                                if ( strpos( $m[1], '%' ) !== false ) {
                                        # Should anchors '#' also be rejected?
-                                       $m[1] = str_replace( array('<', '>'), array('&lt;', '&gt;'), urldecode( $m[1] ) );
+                                       $m[1] = str_replace( array('<', '>'), array('&lt;', '&gt;'), rawurldecode( $m[1] ) );
                                }
                                $trail = $m[3];
                        } elseif ( preg_match( $e1_img, $line, $m ) ) { # Invalid, but might be an image with a link in its caption
                                $might_be_img = true;
                                $text = $m[2];
                                if ( strpos( $m[1], '%' ) !== false ) {
-                                       $m[1] = urldecode( $m[1] );
+                                       $m[1] = rawurldecode( $m[1] );
                                }
                                $trail = "";
                        } else { # Invalid form; output directly
@@ -1846,9 +1822,10 @@ class Parser {
                                $text = $link;
                        } else {
                                # Bug 4598 madness. Handle the quotes only if they come from the alternate part
-                               # [[Lista d''e paise d''o munno]] -> <a href="">Lista d''e paise d''o munno</a>
-                               # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']] -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
-                               $text = $this->doQuotes($text);
+                               # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
+                               # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
+                               #    -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
+                               $text = $this->doQuotes( $text );
                        }
 
                        # Link not escaped by : , create the various objects
@@ -1910,7 +1887,7 @@ class Parser {
                                         * Strip the whitespace Category links produce, see bug 87
                                         * @todo We might want to use trim($tmp, "\n") here.
                                         */
-                                       $s .= trim( $prefix . $trail, "\n" ) == '' ? '': $prefix . $trail;
+                                       $s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
 
                                        wfProfileOut( __METHOD__."-category" );
                                        continue;
@@ -2988,6 +2965,7 @@ class Parser {
                $originalTitle = $part1;
 
                # $args is a list of argument nodes, starting from index 0, not including $part1
+               # *** FIXME if piece['parts'] is null then the call to getLength() below won't work b/c this $args isn't an object
                $args = ( null == $piece['parts'] ) ? array() : $piece['parts'];
                wfProfileOut( __METHOD__.'-setup' );
 
@@ -3457,9 +3435,9 @@ class Parser {
                $text = $frame->getArgument( $argName );
                if (  $text === false && $parts->getLength() > 0
                  && (
-                   $this->ot['html']
-                   || $this->ot['pre']
-                   || ( $this->ot['wiki'] && $frame->isTemplate() )
+                       $this->ot['html']
+                       || $this->ot['pre']
+                       || ( $this->ot['wiki'] && $frame->isTemplate() )
                  )
                ) {
                        # No match in frame, use the supplied default
@@ -3583,7 +3561,7 @@ class Parser {
         * @return Boolean: false if this inclusion would take it over the maximum, true otherwise
         */
        function incrementIncludeSize( $type, $size ) {
-               if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize( $type ) ) {
+               if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
                        return false;
                } else {
                        $this->mIncludeSizes[$type] += $size;
@@ -3650,7 +3628,7 @@ class Parser {
                        $this->mOutput->setIndexPolicy( 'index' );
                        $this->addTrackingCategory( 'index-category' );
                }
-               
+
                # Cache all double underscores in the database
                foreach ( $this->mDoubleUnderscores as $key => $val ) {
                        $this->mOutput->setProperty( $key, '' );
@@ -3704,13 +3682,16 @@ class Parser {
                global $wgMaxTocLevel, $wgContLang, $wgHtml5, $wgExperimentalHtmlIds;
 
                $doNumberHeadings = $this->mOptions->getNumberHeadings();
-               
+
                # Inhibit editsection links if requested in the page
                if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
                        $showEditLink = 0;
                } else {
                        $showEditLink = $this->mOptions->getEditSection();
                }
+               if ( $showEditLink ) {
+                       $this->mOutput->setEditSectionTokens( true );
+               }
 
                # 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
@@ -3755,7 +3736,6 @@ class Parser {
                $head = array();
                $sublevelCount = array();
                $levelCount = array();
-               $toclevel = 0;
                $level = 0;
                $prevlevel = 0;
                $toclevel = 0;
@@ -3769,6 +3749,7 @@ class Parser {
                $node = $root->getFirstChild();
                $byteOffset = 0;
                $tocraw = array();
+               $refers = array();
 
                foreach ( $matches[3] as $headline ) {
                        $isTemplate = false;
@@ -3895,9 +3876,10 @@ class Parser {
                                        'noninitial' );
                        }
 
-                       # HTML names must be case-insensitively unique (bug 10721).  FIXME:
-                       # Does this apply to Unicode characters?  Because we aren't
-                       # handling those here.
+                       # HTML names must be case-insensitively unique (bug 10721).
+                       # This does not apply to Unicode characters per
+                       # http://dev.w3.org/html5/spec/infrastructure.html#case-sensitivity-and-string-comparison
+                       # FIXME: We may be changing them depending on the current locale.
                        $arrayKey = strtolower( $safeHeadline );
                        if ( $legacyHeadline === false ) {
                                $legacyArrayKey = false;
@@ -3942,8 +3924,9 @@ class Parser {
                        while ( $node && !$isTemplate ) {
                                if ( $node->getName() === 'h' ) {
                                        $bits = $node->splitHeading();
-                                       if ( $bits['i'] == $sectionIndex )
+                                       if ( $bits['i'] == $sectionIndex ) {
                                                break;
+                                       }
                                }
                                $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
                                        $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
@@ -3962,12 +3945,27 @@ class Parser {
 
                        # give headline the correct <h#> tag
                        if ( $showEditLink && $sectionIndex !== false ) {
+                               // Output edit section links as markers with styles that can be customized by skins
                                if ( $isTemplate ) {
                                        # Put a T flag in the section identifier, to indicate to extractSections()
                                        # that sections inside <includeonly> should be counted.
-                                       $editlink = $sk->doEditSectionLink( Title::newFromText( $titleText ), "T-$sectionIndex", null, $this->mOptions->getUserLang() );
+                                       $editlinkArgs = array( $titleText, "T-$sectionIndex"/*, null */ );
+                               } else {
+                                       $editlinkArgs = array( $this->mTitle->getPrefixedText(), $sectionIndex, $headlineHint );
+                               }
+                               // We use a bit of pesudo-xml for editsection markers. The language converter is run later on
+                               // Using a UNIQ style marker leads to the converter screwing up the tokens when it converts stuff
+                               // And trying to insert strip tags fails too. At this point all real inputted tags have already been escaped
+                               // so we don't have to worry about a user trying to input one of these markers directly.
+                               // We use a page and section attribute to stop the language converter from converting these important bits
+                               // of data, but put the headline hint inside a content block because the language converter is supposed to
+                               // be able to convert that piece of data.
+                               $editlink = '<editsection page="' . htmlspecialchars($editlinkArgs[0]);
+                               $editlink .= '" section="' . htmlspecialchars($editlinkArgs[1]) .'"';
+                               if ( isset($editlinkArgs[2]) ) {
+                                       $editlink .= '>' . $editlinkArgs[2] . '</editsection>';
                                } else {
-                                       $editlink = $sk->doEditSectionLink( $this->mTitle, $sectionIndex, $headlineHint, $this->mOptions->getUserLang() );
+                                       $editlink .= '/>';
                                }
                        } else {
                                $editlink = '';
@@ -4035,16 +4033,16 @@ class Parser {
         * conversion, substitting signatures, {{subst:}} templates, etc.
         *
         * @param $text String: the text to transform
-        * @param &$title Title: the Title object for the current article
+        * @param $title Title: the Title object for the current article
         * @param $user User: the User object describing the current user
         * @param $options ParserOptions: parsing options
         * @param $clearState Boolean: whether to clear the parser state first
         * @return String: the altered wiki markup
         */
-       public function preSaveTransform( $text, Title $title, $user, $options, $clearState = true ) {
-               $options->resetUsage();
+       public function preSaveTransform( $text, Title $title, User $user, ParserOptions $options, $clearState = true ) {
                $this->mOptions = $options;
                $this->setTitle( $title );
+               $this->setUser( $user );
                $this->setOutputType( self::OT_WIKI );
 
                if ( $clearState ) {
@@ -4057,6 +4055,9 @@ class Parser {
                $text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text );
                $text = $this->pstPass2( $text, $user );
                $text = $this->mStripState->unstripBoth( $text );
+
+               $this->setUser( null ); #Reset
+
                return $text;
        }
 
@@ -4089,9 +4090,9 @@ class Parser {
                # whatever crap the system uses, localised or not, so we cannot
                # ship premade translations.
                $key = 'timezone-' . strtolower( trim( $tzMsg ) );
-               $value = wfMsgForContent( $key );
-               if ( !wfEmptyMsg( $key, $value ) ) {
-                       $tzMsg = $value;
+               $msg = wfMessage( $key )->inContentLanguage();
+               if ( $msg->exists() ) {
+                       $tzMsg = $msg->text();
                }
 
                date_default_timezone_set( $oldtz );
@@ -4220,9 +4221,9 @@ class Parser {
        function cleanSig( $text, $parsing = false ) {
                if ( !$parsing ) {
                        global $wgTitle;
+                       $this->mOptions = new ParserOptions;
                        $this->clearState();
                        $this->setTitle( $wgTitle );
-                       $this->mOptions = new ParserOptions;
                        $this->setOutputType = self::OT_PREPROCESS;
                }
 
@@ -4314,6 +4315,7 @@ class Parser {
         */
        public function setHook( $tag, $callback ) {
                $tag = strtolower( $tag );
+               if ( preg_match( '/[<> \r\n]/', $tag, $m ) ) throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
                $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
                $this->mTagHooks[$tag] = $callback;
                if ( !in_array( $tag, $this->mStripList ) ) {
@@ -4325,6 +4327,7 @@ class Parser {
 
        function setTransparentTagHook( $tag, $callback ) {
                $tag = strtolower( $tag );
+               if ( preg_match( '/[<> \r\n]/', $tag, $m ) ) throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
                $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
                $this->mTransparentTagHooks[$tag] = $callback;
 
@@ -4429,6 +4432,7 @@ class Parser {
         */
        function setFunctionTagHook( $tag, $callback, $flags ) {
                $tag = strtolower( $tag );
+               if ( preg_match( '/[<> \r\n]/', $tag, $m ) ) throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
                $old = isset( $this->mFunctionTagHooks[$tag] ) ?
                        $this->mFunctionTagHooks[$tag] : null;
                $this->mFunctionTagHooks[$tag] = array( $callback, $flags );
@@ -4516,9 +4520,9 @@ class Parser {
                        }
 
                        if ( strpos( $matches[0], '%' ) !== false ) {
-                               $matches[1] = urldecode( $matches[1] );
+                               $matches[1] = rawurldecode( $matches[1] );
                        }
-                       $tp = Title::newFromText( $matches[1] );
+                       $tp = Title::newFromText( $matches[1], NS_FILE );
                        $nt =& $tp;
                        if ( is_null( $nt ) ) {
                                # Bogus title. Ignore these so we don't bomb out later.
@@ -4693,6 +4697,9 @@ class Parser {
                                                                if ( preg_match( "/^($prots)$chars+$/", $value, $m ) ) {
                                                                        $paramName = 'link-url';
                                                                        $this->mOutput->addExternalLink( $value );
+                                                                       if ( $this->mOptions->getExternalLinkTarget() ) {
+                                                                               $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
+                                                                       }
                                                                        $validated = true;
                                                                }
                                                        } else {
@@ -4733,9 +4740,9 @@ class Parser {
 
                # Will the image be presented in a frame, with the caption below?
                $imageIsFramed = isset( $params['frame']['frame'] ) ||
-                                isset( $params['frame']['framed'] ) ||
-                                isset( $params['frame']['thumbnail'] ) ||
-                                isset( $params['frame']['manualthumb'] );
+                                                isset( $params['frame']['framed'] ) ||
+                                                isset( $params['frame']['thumbnail'] ) ||
+                                                isset( $params['frame']['manualthumb'] );
 
                # In the old days, [[Image:Foo|text...]] would set alt text.  Later it
                # came to also set the caption, ordinary text after the image -- which
@@ -4867,9 +4874,9 @@ class Parser {
         */
        private function extractSections( $text, $section, $mode, $newText='' ) {
                global $wgTitle;
+               $this->mOptions = new ParserOptions;
                $this->clearState();
                $this->setTitle( $wgTitle ); # not generally used but removes an ugly failure mode
-               $this->mOptions = new ParserOptions;
                $this->setOutputType( self::OT_PLAIN );
                $outText = '';
                $frame = $this->getPreprocessor()->newFrame();
@@ -4974,6 +4981,15 @@ class Parser {
                return $this->extractSections( $text, $section, "get", $deftext );
        }
 
+       /**
+        * This function returns $oldtext after the content of the section 
+        * specified by $section has been replaced with $text.
+        * 
+        * @param $text String: former text of the article
+        * @param $section Numeric: section identifier
+        * @param $text String: replacing text
+        * #return String: modified text
+        */
        public function replaceSection( $oldtext, $section, $text ) {
                return $this->extractSections( $oldtext, $section, "replace", $text );
        }
@@ -4987,6 +5003,23 @@ class Parser {
                return $this->mRevisionId;
        }
 
+       /**
+        * Get the revision object for $this->mRevisionId
+        *
+        * @return either a Revision object or null
+        */
+       protected function getRevisionObject() {
+               if ( !is_null( $this->mRevisionObject ) ) {
+                       return $this->mRevisionObject;
+               }
+               if ( is_null( $this->mRevisionId ) ) {
+                       return null;
+               }
+
+               $this->mRevisionObject = Revision::newFromId( $this->mRevisionId );
+               return $this->mRevisionObject;
+       }
+
        /**
         * Get the timestamp associated with the current revision, adjusted for
         * the default server-local timestamp
@@ -4994,24 +5027,21 @@ class Parser {
        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, '' );
+
+                       $revObject = $this->getRevisionObject();
+                       $timestamp = $revObject ? $revObject->getTimestamp() : false;
+
+                       if( $timestamp !== false ) {
+                               global $wgContLang;
+
+                               # 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__ );
                }
@@ -5024,16 +5054,18 @@ class Parser {
         * @return String: user name
         */
        function getRevisionUser() {
-               # if this template is subst: the revision id will be blank,
-               # so just use the current user's name
-               if ( $this->mRevisionId ) {
-                       $revision = Revision::newFromId( $this->mRevisionId );
-                       $revuser = $revision->getUserText();
-               } else {
-                       global $wgUser;
-                       $revuser = $wgUser->getName();
+               if( is_null( $this->mRevisionUser ) ) {
+                       $revObject = $this->getRevisionObject();
+
+                       # if this template is subst: the revision id will be blank,
+                       # so just use the current user's name
+                       if( $revObject ) {
+                               $this->mRevisionUser = $revObject->getUserText();
+                       } elseif( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
+                               $this->mRevisionUser = $this->getUser()->getName();
+                       }
                }
-               return $revuser;
+               return $this->mRevisionUser;
        }
 
        /**
@@ -5133,13 +5165,13 @@ class Parser {
         * strip/replaceVariables/unstrip for preprocessor regression testing
         */
        function testSrvus( $text, $title, $options, $outputType = self::OT_HTML ) {
+               $this->mOptions = $options;
                $this->clearState();
                if ( !$title instanceof Title ) {
                        $title = Title::newFromText( $title );
                }
                $this->mTitle = $title;
                $options->resetUsage();
-               $this->mOptions = $options;
                $this->setOutputType( $outputType );
                $text = $this->replaceVariables( $text );
                $text = $this->mStripState->unstripBoth( $text );
@@ -5257,7 +5289,7 @@ class Parser {
         */
        function unserialiseHalfParsedText( $data, $intPrefix = null ) {
                if ( !$intPrefix ) {
-                       $intPrefix = $this->getRandomString();
+                       $intPrefix = self::getRandomString();
                }
 
                # First, extract the strip state.