Revert r85288 (magic accessors for RequestContext); much more trouble than they're...
[lhc/web/wiklou.git] / includes / parser / Parser.php
index 822c966..76974c9 100644 (file)
@@ -34,7 +34,7 @@
  * Globals used:
  *    objects:   $wgLang, $wgContLang
  *
- * NOT $wgArticle, $wgUser or $wgTitle. Keep them away!
+ * NOT $wgUser or $wgTitle. Keep them away!
  *
  * settings:
  *  $wgUseDynamicDates*, $wgInterwikiMagic*,
@@ -68,7 +68,7 @@ class Parser {
 
        # Constants needed for external link processing
        # Everything except bracket, space, or control characters
-       const EXT_LINK_URL_CLASS = '(?:[^\]\[<>"\\x00-\\x20\\x7F]|(?:\[\]))';
+       const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F]';
        const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)([^][<>"\\x00-\\x20\\x7F]+)
                \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sx';
 
@@ -109,12 +109,23 @@ class Parser {
        var $mImageParamsMagicArray = array();
        var $mMarkerIndex = 0;
        var $mFirstCall = true;
-       var $mVariables, $mSubstWords; # Initialised by initialiseVariables()
+
+       # Initialised by initialiseVariables()
+
+       /**
+        * @var MagicWordArray
+        */
+       var $mVariables;
+
+       /**
+        * @var MagicWordArray
+        */
+       var $mSubstWords;
        var $mConf, $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols; # Initialised in constructor
 
        # Cleared with clearState():
        /**
-        * @var OutputPage
+        * @var ParserOutput
         */
        var $mOutput;
        var $mAutonumber, $mDTopen;
@@ -125,7 +136,12 @@ class Parser {
        var $mStripState;
 
        var $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
-       var $mLinkHolders, $mLinkID;
+       /**
+        * @var LinkHolderArray
+        */
+       var $mLinkHolders;
+
+       var $mLinkID;
        var $mIncludeSizes, $mPPNodeCount, $mDefaultSort;
        var $mTplExpandCache; # empty-frame expansion cache
        var $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores;
@@ -153,9 +169,14 @@ class Parser {
        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 $mRevisionUser; # User to display in {{REVISIONUSER}} tag
        var $mRevIdForTs;   # The revision ID which was used to fetch the timestamp
 
+       /**
+        * @var string
+        */
+       var $mUniqPrefix;
+
        /**
         * Constructor
         */
@@ -163,9 +184,12 @@ class Parser {
                $this->mConf = $conf;
                $this->mUrlProtocols = wfUrlProtocols();
                $this->mExtLinkBracketedRegex = '/\[(\b(' . wfUrlProtocols() . ')'.
-                       '(?:[^\]\[<>"\x00-\x20\x7F]|\[\])+) *([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/S';
+                       '[^][<>"\\x00-\\x20\\x7F]+) *([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/S';
                if ( isset( $conf['preprocessorClass'] ) ) {
                        $this->mPreprocessorClass = $conf['preprocessorClass'];
+               } elseif ( defined( 'MW_COMPILED' ) ) {
+                       # Preprocessor_Hash is much faster than Preprocessor_DOM in compiled mode
+                       $this->mPreprocessorClass = 'Preprocessor_Hash';
                } elseif ( extension_loaded( 'domxml' ) ) {
                        # PECL extension that conflicts with the core DOM extension (bug 13770)
                        wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
@@ -175,6 +199,7 @@ class Parser {
                } else {
                        $this->mPreprocessorClass = 'Preprocessor_Hash';
                }
+               wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
        }
 
        /**
@@ -182,7 +207,7 @@ class Parser {
         */
        function __destruct() {
                if ( isset( $this->mLinkHolders ) ) {
-                       $this->mLinkHolders->__destruct();
+                       unset( $this->mLinkHolders );
                }
                foreach ( $this as $name => $value ) {
                        unset( $this->$name );
@@ -278,7 +303,7 @@ class Parser {
         * Do not call this function recursively.
         *
         * @param $text String: text we want to parse
-        * @param $title A title object
+        * @param $title Title object
         * @param $options ParserOptions
         * @param $linestart boolean
         * @param $clearState boolean
@@ -448,6 +473,8 @@ class Parser {
         *
         * @param $text String: text extension wants to have parsed
         * @param $frame PPFrame: The frame to use for expanding any template variables
+        *
+        * @return string
         */
        function recursiveTagParse( $text, $frame=false ) {
                wfProfileIn( __METHOD__ );
@@ -496,7 +523,7 @@ class Parser {
        /**
         * Get a random string
         *
-        * @static
+        * @return string
         */
        static public function getRandomString() {
                return dechex( mt_rand( 0, 0x7fffffff ) ) . dechex( mt_rand( 0, 0x7fffffff ) );
@@ -519,7 +546,7 @@ class Parser {
         */
        public function uniqPrefix() {
                if ( !isset( $this->mUniqPrefix ) ) {
-                       # @todo Fixme: this is probably *horribly wrong*
+                       # @todo FIXME: This is probably *horribly wrong*
                        # LanguageConverter seems to want $wgParser's uniqPrefix, however
                        # if this is called for a parser cache hit, the parser may not
                        # have ever been initialized in the first place.
@@ -532,6 +559,8 @@ class Parser {
 
        /**
         * Set the context title
+        *
+        * @param $t Title
         */
        function setTitle( $t ) {
                if ( !$t || $t instanceof FakeTitle ) {
@@ -620,10 +649,16 @@ class Parser {
                return wfSetVar( $this->mOptions, $x );
        }
 
+       /**
+        * @return int
+        */
        function nextLinkID() {
                return $this->mLinkID++;
        }
 
+       /**
+        * @param $id int
+        */
        function setLinkID( $id ) {
                $this->mLinkID = $id;
        }
@@ -638,7 +673,7 @@ class Parser {
                if ( $target !== null ) {
                        return $target;
                } else {
-                       return $this->mOptions->getInterfaceMessage() ? $wgLang : $wgContLang;
+                       return $this->mOptions->getInterfaceMessage() ? $wgLang : $this->mTitle->getPageLanguage();
                }
        }
 
@@ -679,15 +714,13 @@ class Parser {
         *     array( 'param' => 'x' ),
         *     '<element param="x">tag content</element>' ) )
         *
-        * @param $elements list of element names. Comments are always extracted.
-        * @param $text Source text string.
-        * @param $matches Out parameter, Array: extracted tags
-        * @param $uniq_prefix
+        * @param $elements array list of element names. Comments are always extracted.
+        * @param $text string Source text string.
+        * @param $matches array Out parameter, Array: extracted tags
+        * @param $uniq_prefix string
         * @return String: stripped text
-        *
-        * @static
         */
-       public function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = '' ) {
+       public static function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = '' ) {
                static $n = 1;
                $stripped = '';
                $matches = array();
@@ -751,52 +784,17 @@ class Parser {
 
        /**
         * Get a list of strippable XML-like elements
+        *
+        * @return array
         */
        function getStripList() {
                return $this->mStripList;
        }
 
-       /**
-        * @deprecated use replaceVariables
-        */
-       function strip( $text, $state, $stripcomments = false , $dontstrip = array() ) {
-               return $text;
-       }
-
-       /**
-        * Restores pre, math, and other extensions removed by strip()
-        *
-        * always call unstripNoWiki() after this one
-        * @private
-        * @deprecated use $this->mStripState->unstrip()
-        */
-       function unstrip( $text, $state ) {
-               return $state->unstripGeneral( $text );
-       }
-
-       /**
-        * Always call this after unstrip() to preserve the order
-        *
-        * @private
-        * @deprecated use $this->mStripState->unstrip()
-        */
-       function unstripNoWiki( $text, $state ) {
-               return $state->unstripNoWiki( $text );
-       }
-
-       /**
-        * @deprecated use $this->mStripState->unstripBoth()
-        */
-       function unstripForHTML( $text ) {
-               return $this->mStripState->unstripBoth( $text );
-       }
-
        /**
         * Add an item to the strip state
         * Returns the unique tag which must be inserted into the stripped text
         * The tag will be replaced with the original text in unstrip()
-        *
-        * @private
         */
        function insertStripItem( $text ) {
                $rnd = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
@@ -805,15 +803,6 @@ class Parser {
                return $rnd;
        }
 
-       /**
-        * Interface with html tidy
-        * @deprecated Use MWTidy::tidy()
-        */
-       public static function tidy( $text ) {
-               wfDeprecated( __METHOD__ );
-               return MWTidy::tidy( $text );
-       }
-
        /**
         * parse the wiki syntax used to render tables
         *
@@ -831,7 +820,7 @@ class Parser {
 
                        # empty line, go to next line,
                        # but only append \n if outside of table
-                       if ( $line === '') { 
+                       if ( $line === '') {
                                $output .= $outLine . "\n";
                                continue;
                        }
@@ -846,7 +835,6 @@ class Parser {
                                $table =& $this->last( $tables );
                                $table[0] = array(); // first row
                                $currentRow =& $table[0];
-
                                $table['indent'] = strlen( $matches[1] );
 
                                $attributes = $this->mStripState->unstripBoth( $matches[2] );
@@ -855,11 +843,11 @@ class Parser {
                                if ( $attributes !== '' ) {
                                        $table['attributes'] = $attributes;
                                }
-                       } else if ( !isset( $tables[0] ) ) {
+                       } elseif ( !isset( $tables[0] ) ) {
                                // we're outside the table
 
                                $out .= $outLine . "\n";
-                       } else if ( $firstChars === '|}' ) {
+                       } elseif ( $firstChars === '|}' ) {
                                // trim the |} code from the line
                                $line = substr ( $line , 2 );
 
@@ -881,7 +869,7 @@ class Parser {
                                }
                                $o = '';
                                $curtable = array_pop( $tables );
-                               
+
                                #Add a line-ending before the table, but only if there isn't one already
                                if ( substr( $out, -1 ) !== "\n" ) {
                                        $o .= "\n";
@@ -900,7 +888,7 @@ class Parser {
 
                                $output .= $o;
 
-                       } else if ( $firstChars === '|-' ) {
+                       } elseif ( $firstChars === '|-' ) {
                                // start a new row element
                                // but only when we haven't started one already
                                if ( count( $currentRow ) != 0 ) {
@@ -915,18 +903,19 @@ class Parser {
                                        $currentRow['attributes'] = $attributes;
                                }
 
-                       } else if ( $firstChars  === '|+' ) {
-                               // a table caption
-                               $line = substr ( $line , 2 );
+                       } elseif ( $firstChars  === '|+' ) {
+                               // a table caption, but only proceed if there isn't one already
+                               if ( !isset ( $table['caption'] ) ) {
+                                       $line = substr ( $line , 2 );
 
-                               $c = $this->getCellAttr( $line , 'caption' );
-                               $table['caption'] = array();
-                               $table['caption']['content'] = $c[0];
-                               if ( isset( $c[1] ) ) $table['caption']['attributes'] = $c[1];
-                               unset( $c );
-
-                               $output =& $table['caption'];
-                       } else if ( $firstChars === '|' || $firstChars === '!' || $firstChars === '!+' ) {
+                                       $c = $this->getCellAttr( $line , 'caption' );
+                                       $table['caption'] = array();
+                                       $table['caption']['content'] = $c[0];
+                                       if ( isset( $c[1] ) ) $table['caption']['attributes'] = $c[1];
+                                       unset( $c );
+                                       $output =& $table['caption']['content'];
+                               }
+                       } elseif ( $firstChars === '|' || $firstChars === '!' || $firstChars === '!+' ) {
                                // Which kind of cells are we dealing with
                                $currentTag = 'td';
                                $line = substr ( $line , 1 );
@@ -943,7 +932,7 @@ class Parser {
                                // decide whether thead to tbody
                                if ( !array_key_exists( 'type', $currentRow ) ) {
                                        $currentRow['type'] = ( $firstChars === '!' ) ? 'thead' : 'tbody' ;
-                               } else if ( $firstChars === '|' ) {
+                               } elseif ( $firstChars === '|' ) {
                                        $currentRow['type'] = 'tbody';
                                }
 
@@ -990,16 +979,17 @@ class Parser {
                return $out;
        }
 
-
        /**
         * Helper function for doTableStuff() separating the contents of cells from
-        * attributes. Particularly useful as there's a possible bug and this action 
+        * attributes. Particularly useful as there's a possible bug and this action
         * is repeated twice.
         *
         * @private
+        * @param $cell
+        * @param $tagName
+        * @return array
         */
        function getCellAttr ( $cell, $tagName ) {
-               $content = null;
                $attributes = null;
 
                $cell = trim ( $cell );
@@ -1012,10 +1002,9 @@ class Parser {
                if ( strpos( $cellData[0], '[[' ) !== false ) {
                        $content = trim ( $cell );
                }
-               else if ( count ( $cellData ) == 1 ) {
+               elseif ( count ( $cellData ) == 1 ) {
                        $content = trim ( $cellData[0] );
-               }
-               else {
+               } else {
                        $attributes = $this->mStripState->unstripBoth( $cellData[0] );
                        $attributes = Sanitizer::fixTagAttributes( $attributes , $tagName );
 
@@ -1030,7 +1019,7 @@ class Parser {
         *
         * @private
         */
-       function generateTableHTML ( &$table ) {
+       function generateTableHTML( &$table ) {
                $return = "";
                $return .= str_repeat( '<dl><dd>' , $table['indent'] );
                $return .= '<table';
@@ -1052,21 +1041,20 @@ class Parser {
                // If we only have tbodies, mark table as simple
                for ( $i = 0; isset( $table[$i] ); $i++ ) {
                        if ( !count( $table[$i] ) ) continue;
+                       if ( !isset( $table[$i]['type'] ) ) {
+                               $table[$i]['type'] = 'tbody';
+                       }
                        if ( !$lastSection ) {
                                $lastSection = $table[$i]['type'];
-                       } else if ( $lastSection != $table[$i]['type'] ) {
+                       } elseif ( $lastSection != $table[$i]['type'] ) {
                                $simple = false;
-                               break;
                        }
                }
                $lastSection = '';
                for ( $i = 0; isset( $table[$i] ); $i++ ) {
-                       // Check for empty tables
-                       if ( count( $table[$i] ) ) {
-                               $empty = false;
-                       } else {
-                               continue;
-                       }
+                       if ( !count( $table[$i] ) ) continue;
+                       $empty = false; // check for empty tables
+
                        if ( $table[$i]['type'] != $lastSection && !$simple ) {
                                $return .= "\n<" . $table[$i]['type'] . '>';
                        }
@@ -1075,6 +1063,7 @@ class Parser {
                        $return .= isset( $table[$i]['attributes'] ) ? $table[$i]['attributes'] : '';
                        $return .= '>';
                        for ( $j = 0; isset( $table[$i][$j] ); $j++ ) {
+                               if ( !isset( $table[$i][$j]['type'] ) ) $table[$i][$j]['type'] = 'td';
                                $return .= "\n<" . $table[$i][$j]['type'];
                                $return .= isset( $table[$i][$j]['attributes'] ) ? $table[$i][$j]['attributes'] : '';
                                $return .= '>';
@@ -1088,7 +1077,7 @@ class Parser {
                        }
                        $return .= "\n</tr>";
 
-                       if ( ( !isset( $table[$i + 1] ) && !$simple ) || ( isset( $table[$i + 1] ) && ( $table[$i]['type'] != $table[$i + 1]['type'] ) ) ) {
+                       if ( ( !isset( $table[$i + 1] ) && !$simple ) || ( isset( $table[$i + 1] ) && isset( $table[$i + 1]['type'] ) && $table[$i]['type'] != $table[$i + 1]['type'] ) ) {
                                $return .= '</' . $table[$i]['type'] . '>';
                        }
                        $lastSection = $table[$i]['type'];
@@ -1213,6 +1202,11 @@ class Parser {
                return $text;
        }
 
+       /**
+        * @throws MWException
+        * @param $m array
+        * @return HTML|string
+        */
        function magicLinkCallback( $m ) {
                if ( isset( $m[1] ) && $m[1] !== '' ) {
                        # Skip anchor
@@ -1620,7 +1614,6 @@ class Parser {
                return $attribs;
        }
 
-
        /**
         * Replace unusual URL escape codes with their equivalent characters
         *
@@ -2012,7 +2005,7 @@ class Parser {
                        }
 
                        # NS_MEDIA is a pseudo-namespace for linking directly to a file
-                       # FIXME: Should do batch file existence checks, see comment below
+                       # @todo FIXME: Should do batch file existence checks, see comment below
                        if ( $ns == NS_MEDIA ) {
                                wfProfileIn( __METHOD__."-media" );
                                # Give extensions a chance to select the file revision for us
@@ -2032,7 +2025,7 @@ class Parser {
                        # Some titles, such as valid special pages or files in foreign repos, should
                        # be shown as bluelinks even though they're not included in the page table
                        #
-                       # FIXME: isAlwaysKnown() can be expensive for file links; we should really do
+                       # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
                        # batch file existence checks for NS_FILE and NS_MEDIA
                        if ( $iw == '' && $nt->isAlwaysKnown() ) {
                                $this->mOutput->addLink( $nt );
@@ -2047,18 +2040,6 @@ class Parser {
                return $holders;
        }
 
-       /**
-        * Make a link placeholder. The text returned can be later resolved to a real link with
-        * replaceLinkHolders(). This is done for two reasons: firstly to avoid further
-        * parsing of interwiki links, and secondly to allow all existence checks and
-        * article length checks (for stub links) to be bundled into a single query.
-        *
-        * @deprecated
-        */
-       function makeLinkHolder( &$nt, $text = '', $query = array(), $trail = '', $prefix = '' ) {
-               return $this->mLinkHolders->makeHolder( $nt, $text, $query, $trail, $prefix );
-       }
-
        /**
         * Render a forced-blue link inline; protect against double expansion of
         * URLs if we're in a mode that prepends full URL prefixes to internal links.
@@ -2127,6 +2108,8 @@ class Parser {
        /**#@+
         * Used by doBlockLevels()
         * @private
+        *
+        * @return string
         */
        function closeParagraph() {
                $result = '';
@@ -2151,7 +2134,7 @@ class Parser {
                }
 
                for ( $i = 0; $i < $shorter; ++$i ) {
-                       if ( $st1{$i} != $st2{$i} ) {
+                       if ( $st1[$i] != $st2[$i] ) {
                                break;
                        }
                }
@@ -2162,6 +2145,8 @@ class Parser {
         * These next three functions open, continue, and close the list
         * element appropriate to the prefix character passed into them.
         * @private
+        *
+        * @return string
         */
        function openList( $char ) {
                $result = $this->closeParagraph();
@@ -2186,6 +2171,8 @@ class Parser {
         * TODO: document
         * @param $char String
         * @private
+        *
+        * @return string
         */
        function nextItem( $char ) {
                if ( '*' === $char || '#' === $char ) {
@@ -2210,6 +2197,8 @@ class Parser {
         * TODO: document
         * @param $char String
         * @private
+        *
+        * @return string
         */
        function closeList( $char ) {
                if ( '*' === $char ) {
@@ -2328,7 +2317,7 @@ class Parser {
                                        $output .= $this->openList( $char );
 
                                        if ( ';' === $char ) {
-                                               # FIXME: This is dupe of code above
+                                               # @todo FIXME: This is dupe of code above
                                                if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
                                                        $t = $t2;
                                                        $output .= $term . $this->nextItem( ':' );
@@ -2449,7 +2438,7 @@ class Parser {
                $stack = 0;
                $len = strlen( $str );
                for( $i = 0; $i < $len; $i++ ) {
-                       $c = $str{$i};
+                       $c = $str[$i];
 
                        switch( $state ) {
                        # (Using the number is a performance hack for common cases)
@@ -2585,6 +2574,9 @@ class Parser {
         * Return value of a magic variable (like PAGENAME)
         *
         * @private
+        *
+        * @param $index integer
+        * @param $frame PPFrame
         */
        function getVariableValue( $index, $frame=false ) {
                global $wgContLang, $wgSitename, $wgServer;
@@ -2933,6 +2925,8 @@ class Parser {
         * dependency requirements.
         *
         * @private
+        *
+        * @return PPNode
         */
        function preprocessToDom( $text, $flags = 0 ) {
                $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
@@ -2941,6 +2935,8 @@ class Parser {
 
        /**
         * Return a three-element array: leading whitespace, string contents, trailing whitespace
+        *
+        * @return array
         */
        public static function splitWhitespace( $s ) {
                $ltrimmed = ltrim( $s );
@@ -2971,6 +2967,8 @@ class Parser {
         *        Providing arguments this way may be useful for extensions wishing to perform variable replacement explicitly.
         * @param $argsOnly Boolean: only do argument (triple-brace) expansion, not double-brace expansion
         * @private
+        *
+        * @return string
         */
        function replaceVariables( $text, $frame = false, $argsOnly = false ) {
                # Is there any text? Also, Prevent too big inclusions!
@@ -2994,7 +2992,11 @@ class Parser {
                return $text;
        }
 
-       # Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
+       /**
+        * Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
+        *
+        * @return array
+        */
        static function createAssocArgs( $args ) {
                $assocArgs = array();
                $index = 1;
@@ -3080,9 +3082,10 @@ 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
+               # @todo 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' );
+               wfProfileIn( __METHOD__."-title-$originalTitle" );
 
                # SUBST
                wfProfileIn( __METHOD__.'-modifiers' );
@@ -3251,7 +3254,7 @@ class Parser {
                                        && $this->mOptions->getAllowSpecialInclusion()
                                        && $this->ot['html'] )
                                {
-                                       $text = SpecialPage::capturePath( $title );
+                                       $text = SpecialPageFactory::capturePath( $title );
                                        if ( is_string( $text ) ) {
                                                $found = true;
                                                $isHTML = true;
@@ -3301,6 +3304,7 @@ class Parser {
                # Recover the source wikitext and return it
                if ( !$found ) {
                        $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
+                       wfProfileOut( __METHOD__."-title-$originalTitle" );
                        wfProfileOut( __METHOD__ );
                        return array( 'object' => $text );
                }
@@ -3369,6 +3373,7 @@ class Parser {
                        $ret = array( 'text' => $text );
                }
 
+               wfProfileOut( __METHOD__."-title-$originalTitle" );
                wfProfileOut( __METHOD__ );
                return $ret;
        }
@@ -3376,6 +3381,8 @@ class Parser {
        /**
         * Get the semi-parsed DOM representation of a template with a given title,
         * and its redirect destination title. Cached.
+        *
+        * @return array
         */
        function getTemplateDom( $title ) {
                $cacheTitle = $title;
@@ -3440,8 +3447,10 @@ class Parser {
        /**
         * Static function to get a template
         * Can be overridden via ParserOptions::setTemplateCallback().
+        *
+        * @return array
         */
-       static function statelessFetchTemplate( $title, $parser=false ) {
+       static function statelessFetchTemplate( $title, $parser = false ) {
                $text = $skip = false;
                $finalTitle = $title;
                $deps = array();
@@ -3543,9 +3552,7 @@ class Parser {
                # Register the file as a dependency...
                $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
                if ( $file && !$title->equals( $file->getTitle() ) ) {
-                       # We fetched a rev from a different title; register it too...
-                       $this->mOutput->addImage( $file->getTitle()->getDBkey(), $time, $sha1 );
-                       # Update fetched file title 
+                       # Update fetched file title
                        $title = $file->getTitle();
                }
                return array( $file, $title );
@@ -3553,6 +3560,11 @@ class Parser {
 
        /**
         * Transclude an interwiki link.
+        *
+        * @param $title Title
+        * @param $action
+        *
+        * @return string
         */
        function interwikiTransclude( $title, $action ) {
                global $wgEnableScaryTranscluding;
@@ -3569,6 +3581,10 @@ class Parser {
                return $this->fetchScaryTemplateMaybeFromCache( $url );
        }
 
+       /**
+        * @param $url string
+        * @return Mixed|String
+        */
        function fetchScaryTemplateMaybeFromCache( $url ) {
                global $wgTranscludeCacheExpiry;
                $dbr = wfGetDB( DB_SLAVE );
@@ -3593,10 +3609,14 @@ class Parser {
                return $text;
        }
 
-
        /**
         * Triple brace replacement -- used for template arguments
         * @private
+        *
+        * @param $peice array
+        * @param $frame PPFrame
+        *
+        * @return array
         */
        function argSubstitution( $piece, $frame ) {
                wfProfileIn( __METHOD__ );
@@ -3650,6 +3670,8 @@ class Parser {
         *     inner      Contents of extension element
         *     noClose    Original text did not have a close tag
         * @param $frame PPFrame
+        *
+        * @return string
         */
        function extensionSubstitution( $params, $frame ) {
                $name = $frame->expand( $params['name'] );
@@ -3792,7 +3814,7 @@ class Parser {
                }
                # (bug 8068) Allow control over whether robots index a page.
                #
-               # FIXME (bug 14899): __INDEX__ always overrides __NOINDEX__ here!  This
+               # @todo FIXME: Bug 14899: __INDEX__ always overrides __NOINDEX__ here!  This
                # is not desirable, the last one on the page should win.
                if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
                        $this->mOutput->setIndexPolicy( 'noindex' );
@@ -3990,7 +4012,10 @@ class Parser {
                                        if ( $dot ) {
                                                $numbering .= '.';
                                        }
-                                       $numbering .= $wgContLang->formatNum( $sublevelCount[$i] );
+                                       global $wgBetterDirectionality;
+                                       $pagelang = $this->mTitle->getPageLanguage();
+                                       $toclang = ( $wgBetterDirectionality ? $pagelang : $wgContLang );
+                                       $numbering .= $toclang->formatNum( $sublevelCount[$i] );
                                        $dot = 1;
                                }
                        }
@@ -4048,7 +4073,7 @@ class Parser {
                        # 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.
+                       # @todo FIXME: We may be changing them depending on the current locale.
                        $arrayKey = strtolower( $safeHeadline );
                        if ( $legacyHeadline === false ) {
                                $legacyArrayKey = false;
@@ -4361,11 +4386,9 @@ class Parser {
                # If we're still here, make it a link to the user page
                $userText = wfEscapeWikiText( $username );
                $nickText = wfEscapeWikiText( $nickname );
-               if ( $user->isAnon() )  {
-                       return wfMsgExt( 'signature-anon', array( 'content', 'parsemag' ), $userText, $nickText );
-               } else {
-                       return wfMsgExt( 'signature', array( 'content', 'parsemag' ), $userText, $nickText );
-               }
+               $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
+
+               return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()->title( $this->getTitle() )->text();
        }
 
        /**
@@ -4402,7 +4425,7 @@ class Parser {
                        return $text;
                }
 
-               # FIXME: regex doesn't respect extension tags or nowiki
+               # @todo FIXME: Regex doesn't respect extension tags or nowiki
                #  => Move this logic to braceSubstitution()
                $substWord = MagicWord::get( 'subst' );
                $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
@@ -4486,11 +4509,22 @@ class Parser {
        /**
         * Create an HTML-style tag, e.g. <yourtag>special text</yourtag>
         * The callback should have the following form:
-        *    function myParserHook( $text, $params, $parser ) { ... }
+        *    function myParserHook( $text, $params, $parser, $frame ) { ... }
         *
         * Transform and return $text. Use $parser for any required context, e.g. use
         * $parser->getTitle() and $parser->getOptions() not $wgTitle or $wgOut->mParserOptions
         *
+        * Hooks may return extended information by returning an array, of which the
+        * first numbered element (index 0) must be the return string, and all other
+        * entries are extracted into local variables within an internal function
+        * in the Parser class.
+        *
+        * This interface (introduced r61913) appears to be undocumented, but
+        * 'markerName' is used by some core tag hooks to override which strip
+        * array their results are placed in. **Use great caution if attempting
+        * this interface, as it is not documented and injudicious use could smash
+        * private variables.**
+        *
         * @param $tag Mixed: the tag to use, e.g. 'hook' for <hook>
         * @param $callback Mixed: the callback function (and object) to use for the tag
         * @return The old value of the mTagHooks array associated with the hook
@@ -4507,6 +4541,22 @@ class Parser {
                return $oldVal;
        }
 
+       /**
+        * As setHook(), but letting the contents be parsed.
+        *
+        * Transparent tag hooks are like regular XML-style tag hooks, except they
+        * operate late in the transformation sequence, on HTML instead of wikitext.
+        *
+        * This is probably obsoleted by things dealing with parser frames?
+        * The only extension currently using it is geoserver.
+        *
+        * @since 1.10
+        * @todo better document or deprecate this
+        *
+        * @param $tag Mixed: the tag to use, e.g. 'hook' for <hook>
+        * @param $callback Mixed: the callback function (and object) to use for the tag
+        * @return The old value of the mTagHooks array associated with the hook
+        */
        function setTransparentTagHook( $tag, $callback ) {
                $tag = strtolower( $tag );
                if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
@@ -4524,6 +4574,19 @@ class Parser {
                $this->mStripList = $this->mDefaultStripList;
        }
 
+       /**
+        * Remove a specific tag hook. Should not be called on $wgParser.
+        * Does not change the strip list.
+        *
+        * @param string $tag
+        * @return void
+        */
+       function clearTagHook( $tag ) {
+               if ( isset( $this->mTagHooks[$tag] ) ) {
+                       unset( $this->mTagHooks[$tag] );
+               }
+       }
+
        /**
         * Create a function, e.g. {{sum:1|2|3}}
         * The callback function should have the form:
@@ -4627,7 +4690,7 @@ class Parser {
        }
 
        /**
-        * FIXME: update documentation. makeLinkObj() is deprecated.
+        * @todo FIXME: Update documentation. makeLinkObj() is deprecated.
         * Replace <!--LINK--> link placeholders with actual links, in the buffer
         * Placeholders created in Skin::makeLinkObj()
         * Returns an array of link CSS classes, indexed by PDBK.
@@ -4655,6 +4718,10 @@ class Parser {
         * given as text will return the HTML of a gallery with two images,
         * labeled 'The number "1"' and
         * 'A tree'.
+        *
+        * @param string $text
+        * @param array $param
+        * @return string HTML
         */
        function renderImageGallery( $text, $params ) {
                $ig = new ImageGallery();
@@ -4702,21 +4769,38 @@ class Parser {
                        if ( strpos( $matches[0], '%' ) !== false ) {
                                $matches[1] = rawurldecode( $matches[1] );
                        }
-                       $tp = Title::newFromText( $matches[1], NS_FILE );
-                       $nt =& $tp;
-                       if ( is_null( $nt ) ) {
+                       $title = Title::newFromText( $matches[1], NS_FILE );
+                       if ( is_null( $title ) ) {
                                # Bogus title. Ignore these so we don't bomb out later.
                                continue;
                        }
+
+                       $label = '';
+                       $alt = '';
                        if ( isset( $matches[3] ) ) {
-                               $label = $matches[3];
-                       } else {
-                               $label = '';
+                               // look for an |alt= definition while trying not to break existing
+                               // captions with multiple pipes (|) in it, until a more sensible grammar
+                               // is defined for images in galleries
+
+                               $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
+                               $altmatches = StringUtils::explode('|', $matches[3]);
+                               $magicWordAlt = MagicWord::get( 'img_alt' );
+
+                               foreach ( $altmatches as $altmatch ) {
+                                       $match = $magicWordAlt->matchVariableStartToEnd( $altmatch );
+                                       if ( $match ) {
+                                               $alt = $this->stripAltText( $match, false );
+                                       }
+                                       else {
+                                               // concatenate all other pipes
+                                               $label .= '|' . $altmatch;
+                                       }
+                               }
+                               // remove the first pipe
+                               $label = substr( $label, 1 );
                        }
 
-                       $html = $this->recursiveTagParse( trim( $label ) );
-
-                       $ig->add( $nt, $html );
+                       $ig->add( $title, $label, $alt );
                }
                return $ig->toHTML();
        }
@@ -4809,6 +4893,10 @@ class Parser {
 
                list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
 
+               if ( !$file ) {
+                       $this->addTrackingCategory( 'broken-file-category' );
+               }
+
                # Process the input parameters
                $caption = '';
                $params = array( 'frame' => array(), 'handler' => array(),
@@ -4852,7 +4940,7 @@ class Parser {
                                                switch( $paramName ) {
                                                case 'manualthumb':
                                                case 'alt':
-                                                       # @todo Fixme: possibly check validity here for
+                                                       # @todo FIXME: Possibly check validity here for
                                                        # manualthumb? downstream behavior seems odd with
                                                        # missing manual thumbs.
                                                        $validated = true;
@@ -4969,6 +5057,11 @@ class Parser {
                return $ret;
        }
 
+       /**
+        * @param $caption
+        * @param $holders LinkHolderArray
+        * @return mixed|String
+        */
        protected function stripAltText( $caption, $holders ) {
                # Strip bad stuff out of the title (tooltip).  We can't just use
                # replaceLinkHoldersText() here, because if this function is called
@@ -5005,7 +5098,6 @@ class Parser {
         * @param $text String
         * @param $frame PPFrame
         * @return String
-        * @private
         */
        function attributeStripCallback( &$text, $frame = false ) {
                $text = $this->replaceVariables( $text, $frame );
@@ -5015,6 +5107,8 @@ class Parser {
 
        /**
         * Accessor
+        *
+        * @return array
         */
        function getTags() {
                return array_merge( array_keys( $this->mTransparentTagHooks ), array_keys( $this->mTagHooks ) );
@@ -5029,7 +5123,8 @@ class Parser {
        function replaceTransparentTags( $text ) {
                $matches = array();
                $elements = array_keys( $this->mTransparentTagHooks );
-               $text = $this->extractTagsAndParams( $elements, $text, $matches, $this->mUniqPrefix );
+               $text = self::extractTagsAndParams( $elements, $text, $matches, $this->mUniqPrefix );
+               $replacements = array();
 
                foreach ( $matches as $marker => $data ) {
                        list( $element, $content, $params, $tag ) = $data;
@@ -5039,9 +5134,9 @@ class Parser {
                        } else {
                                $output = $tag;
                        }
-                       $this->mStripState->addGeneral( $marker, $output );
+                       $replacements[$marker] = $output;
                }
-               return $text;
+               return strtr( $text, $replacements );
        }
 
        /**
@@ -5068,6 +5163,8 @@ class Parser {
         * @param $newText String: replacement text for section data.
         * @return String: for "get", the extracted section text.
         *                 for "replace", the whole page with the section replaced.
+        *                 If the page is empty and section 0 is requested, $text (as '')
+        *                  is returned
         */
        private function extractSections( $text, $section, $mode, $newText='' ) {
                global $wgTitle; # not generally used but removes an ugly failure mode
@@ -5183,10 +5280,10 @@ class Parser {
         * 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 $oldtext String: former text of the article
         * @param $section Numeric: section identifier
         * @param $text String: replacing text
-        * #return String: modified text
+        * @return String: modified text
         */
        public function replaceSection( $oldtext, $section, $text ) {
                return $this->extractSections( $oldtext, $section, "replace", $text );
@@ -5204,7 +5301,7 @@ class Parser {
        /**
         * Get the revision object for $this->mRevisionId
         *
-        * @return either a Revision object or null
+        * @return Revision|null either a Revision object or null
         */
        protected function getRevisionObject() {
                if ( !is_null( $this->mRevisionObject ) ) {
@@ -5350,7 +5447,8 @@ class Parser {
                $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
                $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
 
-               # Strip external link markup (FIXME: Not Tolerant to blank link text
+               # Strip external link markup
+               # @todo FIXME: Not tolerant to blank link text
                # I.E. [http://www.mediawiki.org] will render as [1] or something depending
                # on how many empty links there are on the page - need to figure that out.
                $text = preg_replace( '/\[(?:' . wfUrlProtocols() . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
@@ -5365,11 +5463,10 @@ class Parser {
 
        /**
         * strip/replaceVariables/unstrip for preprocessor regression testing
+        *
+        * @return string
         */
-       function testSrvus( $text, $title, ParserOptions $options, $outputType = self::OT_HTML ) {
-               if ( !$title instanceof Title ) {
-                       $title = Title::newFromText( $title );
-               }
+       function testSrvus( $text, Title $title, ParserOptions $options, $outputType = self::OT_HTML ) {
                $this->startParse( $title, $options, $outputType, true );
 
                $text = $this->replaceVariables( $text );
@@ -5378,18 +5475,11 @@ class Parser {
                return $text;
        }
 
-       function testPst( $text, $title, $options ) {
-               global $wgUser;
-               if ( !$title instanceof Title ) {
-                       $title = Title::newFromText( $title );
-               }
-               return $this->preSaveTransform( $text, $title, $wgUser, $options );
+       function testPst( $text, Title $title, ParserOptions $options ) {
+               return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
        }
 
-       function testPreprocess( $text, $title, $options ) {
-               if ( !$title instanceof Title ) {
-                       $title = Title::newFromText( $title );
-               }
+       function testPreprocess( $text, Title $title, ParserOptions $options ) {
                return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
        }
 
@@ -5403,6 +5493,8 @@ class Parser {
         * This will call the callback function twice, with 'aaa' and 'bbb'. Those
         * two strings will be replaced with the value returned by the callback in
         * each case.
+        *
+        * @return string
         */
        function markerSkipCallback( $s, $callback ) {
                $i = 0;
@@ -5439,6 +5531,8 @@ class Parser {
         * array can later be loaded into another parser instance with
         * unserializeHalfParsedText(). The text can then be safely incorporated into
         * the return value of a parser hook.
+        *
+        * @return array
         */
        function serializeHalfParsedText( $text ) {
                wfProfileIn( __METHOD__ );
@@ -5487,7 +5581,9 @@ class Parser {
         * serializeHalfParsedText(), is compatible with the current version of the
         * parser.
         *
-        * @param $data Array.
+        * @param $data Array
+        *
+        * @return bool
         */
        function isValidHalfParsedText( $data ) {
                return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;