Woops, fix bug with last commit when the number of rows <= 2.
[lhc/web/wiklou.git] / includes / Parser.php
index 06a1c9c..2899a11 100644 (file)
@@ -130,34 +130,39 @@ class Parser
                global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions;
 
                $this->setHook( 'pre', array( $this, 'renderPreTag' ) );
-               
-               $this->setFunctionHook( 'int', array( 'CoreParserFunctions', 'intFunction' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'ns', array( 'CoreParserFunctions', 'ns' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'urlencode', array( 'CoreParserFunctions', 'urlencode' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'lcfirst', array( 'CoreParserFunctions', 'lcfirst' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'ucfirst', array( 'CoreParserFunctions', 'ucfirst' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'lc', array( 'CoreParserFunctions', 'lc' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'uc', array( 'CoreParserFunctions', 'uc' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'localurl', array( 'CoreParserFunctions', 'localurl' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'localurle', array( 'CoreParserFunctions', 'localurle' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'fullurl', array( 'CoreParserFunctions', 'fullurl' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'fullurle', array( 'CoreParserFunctions', 'fullurle' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'formatnum', array( 'CoreParserFunctions', 'formatnum' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'grammar', array( 'CoreParserFunctions', 'grammar' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'plural', array( 'CoreParserFunctions', 'plural' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'numberofpages', array( 'CoreParserFunctions', 'numberofpages' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'numberofusers', array( 'CoreParserFunctions', 'numberofusers' ), SFH_NO_HASH );
+
+               # Syntax for arguments (see self::setFunctionHook):
+               #  "name for lookup in localized magic words array",
+               #  function callback,
+               #  optional SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}
+               #    instead of {{#int:...}})
+               $this->setFunctionHook( 'int',              array( 'CoreParserFunctions', 'intFunction'      ), SFH_NO_HASH );
+               $this->setFunctionHook( 'ns',               array( 'CoreParserFunctions', 'ns'               ), SFH_NO_HASH );
+               $this->setFunctionHook( 'urlencode',        array( 'CoreParserFunctions', 'urlencode'        ), SFH_NO_HASH );
+               $this->setFunctionHook( 'lcfirst',          array( 'CoreParserFunctions', 'lcfirst'          ), SFH_NO_HASH );
+               $this->setFunctionHook( 'ucfirst',          array( 'CoreParserFunctions', 'ucfirst'          ), SFH_NO_HASH );
+               $this->setFunctionHook( 'lc',               array( 'CoreParserFunctions', 'lc'               ), SFH_NO_HASH );
+               $this->setFunctionHook( 'uc',               array( 'CoreParserFunctions', 'uc'               ), SFH_NO_HASH );
+               $this->setFunctionHook( 'localurl',         array( 'CoreParserFunctions', 'localurl'         ), SFH_NO_HASH );
+               $this->setFunctionHook( 'localurle',        array( 'CoreParserFunctions', 'localurle'        ), SFH_NO_HASH );
+               $this->setFunctionHook( 'fullurl',          array( 'CoreParserFunctions', 'fullurl'          ), SFH_NO_HASH );
+               $this->setFunctionHook( 'fullurle',         array( 'CoreParserFunctions', 'fullurle'         ), SFH_NO_HASH );
+               $this->setFunctionHook( 'formatnum',        array( 'CoreParserFunctions', 'formatnum'        ), SFH_NO_HASH );
+               $this->setFunctionHook( 'grammar',          array( 'CoreParserFunctions', 'grammar'          ), SFH_NO_HASH );
+               $this->setFunctionHook( 'plural',           array( 'CoreParserFunctions', 'plural'           ), SFH_NO_HASH );
+               $this->setFunctionHook( 'numberofpages',    array( 'CoreParserFunctions', 'numberofpages'    ), SFH_NO_HASH );
+               $this->setFunctionHook( 'numberofusers',    array( 'CoreParserFunctions', 'numberofusers'    ), SFH_NO_HASH );
                $this->setFunctionHook( 'numberofarticles', array( 'CoreParserFunctions', 'numberofarticles' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'numberoffiles', array( 'CoreParserFunctions', 'numberoffiles' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'numberofadmins', array( 'CoreParserFunctions', 'numberofadmins' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'numberofedits', array( 'CoreParserFunctions', 'numberofedits' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'language', array( 'CoreParserFunctions', 'language' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'padleft', array( 'CoreParserFunctions', 'padleft' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'padright', array( 'CoreParserFunctions', 'padright' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'anchorencode', array( 'CoreParserFunctions', 'anchorencode' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'special', array( 'CoreParserFunctions', 'special' ) );
-               $this->setFunctionHook( 'defaultsort', array( 'CoreParserFunctions', 'defaultsort' ), SFH_NO_HASH );
-               $this->setFunctionHook( 'filepath', array( 'CoreParserFunctions', 'filepath' ), SFH_NO_HASH );
+               $this->setFunctionHook( 'numberoffiles',    array( 'CoreParserFunctions', 'numberoffiles'    ), SFH_NO_HASH );
+               $this->setFunctionHook( 'numberofadmins',   array( 'CoreParserFunctions', 'numberofadmins'   ), SFH_NO_HASH );
+               $this->setFunctionHook( 'numberofedits',    array( 'CoreParserFunctions', 'numberofedits'    ), SFH_NO_HASH );
+               $this->setFunctionHook( 'language',         array( 'CoreParserFunctions', 'language'         ), SFH_NO_HASH );
+               $this->setFunctionHook( 'padleft',          array( 'CoreParserFunctions', 'padleft'          ), SFH_NO_HASH );
+               $this->setFunctionHook( 'padright',         array( 'CoreParserFunctions', 'padright'         ), SFH_NO_HASH );
+               $this->setFunctionHook( 'anchorencode',     array( 'CoreParserFunctions', 'anchorencode'     ), SFH_NO_HASH );
+               $this->setFunctionHook( 'special',          array( 'CoreParserFunctions', 'special'          ) );
+               $this->setFunctionHook( 'defaultsort',      array( 'CoreParserFunctions', 'defaultsort'      ), SFH_NO_HASH );
+               $this->setFunctionHook( 'filepath',         array( 'CoreParserFunctions', 'filepath'         ), SFH_NO_HASH );
 
                if ( $wgAllowDisplayTitle ) {
                        $this->setFunctionHook( 'displaytitle', array( 'CoreParserFunctions', 'displaytitle' ), SFH_NO_HASH );
@@ -250,6 +255,15 @@ class Parser
         * @public
         */
        function uniqPrefix() {
+               if( !isset( $this->mUniqPrefix ) ) {
+                       // @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.
+                       // Not really sure what the heck is supposed to be going on here.
+                       return '';
+                       //throw new MWException( "Accessing uninitialized mUniqPrefix" );
+               }
                return $this->mUniqPrefix;
        }
 
@@ -375,14 +389,14 @@ class Parser
                wfRunHooks( 'ParserAfterTidy', array( &$this, &$text ) );
 
                # Information on include size limits, for the benefit of users who try to skirt them
-               if ( max( $this->mIncludeSizes ) > 1000 ) {
+               if ( $this->mOptions->getEnableLimitReport() ) {
                        $max = $this->mOptions->getMaxIncludeSize();
-                       $text .= "<!-- \n" .
-                               "Preprocessor node count: {$this->mPPNodeCount}\n" .
-                               "Post-expand include size: {$this->mIncludeSizes['post-expand']} bytes\n" .
-                               "Template argument size: {$this->mIncludeSizes['arg']} bytes\n" .
-                               "Maximum: $max bytes\n" .
-                               "-->\n";
+                       $limitReport = 
+                               "Preprocessor node count: {$this->mPPNodeCount}/{$this->mOptions->mMaxPPNodeCount}\n" .
+                               "Post-expand include size: {$this->mIncludeSizes['post-expand']}/$max bytes\n" .
+                               "Template argument size: {$this->mIncludeSizes['arg']}/$max bytes\n";
+                       wfRunHooks( 'ParserLimitReport', array( $this, &$limitReport ) );
+                       $text .= "\n<!-- \n$limitReport-->\n";
                }
                $this->mOutput->setText( $text );
                $this->mRevisionId = $oldRevisionId;
@@ -588,6 +602,7 @@ class Parser
        function insertStripItem( $text ) {
                static $n = 0;
                $rnd = "{$this->mUniqPrefix}-item-$n-{$this->mMarkerSuffix}";
+               ++$n;
                $this->mStripState->general->setPair( $rnd, $text );
                return $rnd;
        }
@@ -680,7 +695,7 @@ class Parser
         * @static
         */
        function internalTidy( $text ) {
-               global $wgTidyConf, $IP;
+               global $wgTidyConf, $IP, $wgDebugTidy;
                $fname = 'Parser::internalTidy';
                wfProfileIn( $fname );
 
@@ -694,6 +709,12 @@ class Parser
                } else {
                        $cleansource = tidy_get_output( $tidy );
                }
+               if ( $wgDebugTidy && $tidy->getStatus() > 0 ) {
+                       $cleansource .= "<!--\nTidy reports:\n" . 
+                               str_replace( '-->', '--&gt;', $tidy->errorBuffer ) . 
+                               "\n-->";
+               }
+
                wfProfileOut( $fname );
                return $cleansource;
        }
@@ -3020,7 +3041,7 @@ class Parser
 
                $dom = $this->preprocessToDom( $text );
                $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
-               $text = $frame->expand( $dom, 0, $flags );
+               $text = $frame->expand( $dom, $flags );
 
                wfProfileOut( $fname );
                return $text;
@@ -3324,7 +3345,7 @@ class Parser
                                # Just replace the arguments, not any double-brace items
                                # This is used for rendered interwiki transclusion
                                if ( $isDOM ) {
-                                       $text = $newFrame->expand( $text, 0, PPFrame::NO_TEMPLATES );
+                                       $text = $newFrame->expand( $text, PPFrame::NO_TEMPLATES );
                                } else {
                                        $text = $this->replaceVariables( $text, $newFrame, true );
                                }
@@ -3332,7 +3353,7 @@ class Parser
                                $text = $frame->expand( $text );
                        }
                } elseif ( $isDOM ) {
-                       $text = $frame->expand( $text, 0, PPFrame::NO_TEMPLATES | PPFrame::NO_ARGS );
+                       $text = $frame->expand( $text, PPFrame::NO_TEMPLATES | PPFrame::NO_ARGS );
                }
 
                # Prune lower levels off the recursion check path
@@ -4109,9 +4130,9 @@ class Parser
                $userText = wfEscapeWikiText( $username );
                $nickText = wfEscapeWikiText( $nickname );
                if ( $user->isAnon() )  {
-                       return wfMsgForContent( 'signature-anon', $userText, $nickText );
+                       return wfMsgExt( 'signature-anon', array( 'content', 'parsemag' ), $userText, $nickText );
                } else {
-                       return wfMsgForContent( 'signature', $userText, $nickText );
+                       return wfMsgExt( 'signature', array( 'content', 'parsemag' ), $userText, $nickText );
                }
        }
 
@@ -4136,18 +4157,27 @@ class Parser
         * @return string Signature text
         */
        function cleanSig( $text, $parsing = false ) {
-               global $wgTitle;
-               $this->startExternalParse( $wgTitle, new ParserOptions(), $parsing ? OT_WIKI : OT_MSG );
+               if ( !$parsing ) {
+                       global $wgTitle;
+                       $this->startExternalParse( $wgTitle, new ParserOptions(), OT_MSG );
+               }
 
+               # FIXME: regex doesn't respect extension tags or nowiki
+               #  => Move this logic to braceSubstitution()
                $substWord = MagicWord::get( 'subst' );
                $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
                $substText = '{{' . $substWord->getSynonym( 0 );
 
                $text = preg_replace( $substRegex, $substText, $text );
                $text = $this->cleanSigInSig( $text );
-               $text = $this->replaceVariables( $text );
+               $dom = $this->preprocessToDom( $text );
+               $frame = new PPFrame( $this );
+               $text = $frame->expand( $dom->documentElement );
+
+               if ( !$parsing ) {
+                       $text = $this->mStripState->unstripBoth( $text );
+               }
 
-               $this->clearState();
                return $text;
        }
 
@@ -4211,6 +4241,7 @@ class Parser
                $this->setOutputType( OT_MSG );
                $this->clearState();
                $text = $this->replaceVariables( $text );
+               $text = $this->mStripState->unstripBoth( $text );
 
                $executing = false;
                wfProfileOut($fname);
@@ -4535,7 +4566,7 @@ class Parser
                                $pdbk = $pdbks[$key];
                                $searchkey = "<!--LINK $key-->";
                                $title = $this->mLinkHolders['titles'][$key];
-                               if ( !isset( $colours[$pdbk] ) ) {
+                               if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] == 'new' ) {
                                        $linkCache->addBadLinkObj( $title );
                                        $colours[$pdbk] = 'new';
                                        $this->mOutput->addLink( $title, 0 );
@@ -4950,7 +4981,7 @@ class Parser
                                        $curIndex++;
                                }
                                if ( $mode == 'replace' ) {
-                                       $outText .= $frame->expand( $node );
+                                       $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
                                }
                                $node = $node->nextSibling;
                        }
@@ -4978,7 +5009,7 @@ class Parser
                                }
                        }
                        if ( $mode == 'get' ) {
-                               $outText .= $frame->expand( $node );
+                               $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
                        }
                        $node = $node->nextSibling;
                } while ( $node );
@@ -4990,7 +5021,7 @@ class Parser
                        // stripped by the editor, so we need both newlines to restore the paragraph gap
                        $outText .= $newText . "\n\n";
                        while ( $node ) {
-                               $outText .= $frame->expand( $node );
+                               $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
                                $node = $node->nextSibling;
                        }
                }
@@ -5212,6 +5243,7 @@ class PPFrame {
 
        const NO_ARGS = 1;
        const NO_TEMPLATES = 2;
+       const RECOVER_ORIG = 3;
 
        /**
         * Construct a new preprocessor frame.
@@ -5260,7 +5292,7 @@ class PPFrame {
         * using the current context
         * @param $root the node
         */
-       function expand( $root, $shallowFlags = 0, $deepFlags = 0 ) {
+       function expand( $root, $flags = 0 ) {
                if ( is_string( $root ) ) {
                        return $root;
                }
@@ -5270,17 +5302,16 @@ class PPFrame {
                {
                        return $this->parser->insertStripItem( '<!-- node-count limit exceeded -->' );
                }
-               $flags = $shallowFlags | $deepFlags;
 
                if ( is_array( $root ) ) {
                        $s = '';
                        foreach ( $root as $node ) {
-                               $s .= $this->expand( $node, 0, $deepFlags );
+                               $s .= $this->expand( $node, $flags );
                        }
                } elseif ( $root instanceof DOMNodeList ) {
                        $s = '';
                        foreach ( $root as $node ) {
-                               $s .= $this->expand( $node, 0, $deepFlags );
+                               $s .= $this->expand( $node, $flags );
                        }
                } elseif ( $root instanceof DOMNode ) {
                        if ( $root->nodeType == XML_TEXT_NODE ) {
@@ -5292,7 +5323,7 @@ class PPFrame {
                                $title = $titles->item( 0 );
                                $parts = $xpath->query( 'part', $root );
                                if ( $flags & self::NO_TEMPLATES ) {
-                                       $s = '{{' . $this->implodeWithFlags( '|', 0, $deepFlags, $title, $parts ) . '}}';
+                                       $s = '{{' . $this->implodeWithFlags( '|', $flags, $title, $parts ) . '}}';
                                } else {
                                        $lineStart = $root->getAttribute( 'lineStart' );
                                        $params = array( 
@@ -5309,7 +5340,7 @@ class PPFrame {
                                $title = $titles->item( 0 );
                                $parts = $xpath->query( 'part', $root );
                                if ( $flags & self::NO_ARGS || $this->parser->ot['msg'] ) {
-                                       $s = '{{{' . $this->implode( '|', 0, $deepFlags, $title, $parts ) . '}}}';
+                                       $s = '{{{' . $this->implodeWithFlags( '|', $flags, $title, $parts ) . '}}}';
                                } else {
                                        $params = array( 'title' => $title, 'parts' => $parts, 'text' => 'FIXME' );
                                        $s = $this->parser->argSubstitution( $params, $this );
@@ -5330,7 +5361,7 @@ class PPFrame {
                                $s = $this->parser->extensionSubstitution( $params, $this );
                        } elseif ( $root->nodeName == 'h' ) {
                                # Heading
-                               $s = $this->expand( $root->childNodes, 0, $deepFlags );
+                               $s = $this->expand( $root->childNodes, $flags );
 
                                if ( $this->parser->ot['html'] ) {
                                        # Insert heading index marker
@@ -5340,21 +5371,7 @@ class PPFrame {
                                        $serial = count( $this->parser->mHeadings ) - 1;
                                        $marker = "{$this->parser->mUniqPrefix}-h-$serial-{$this->parser->mMarkerSuffix}";
                                        $count = $root->getAttribute( 'level' );
-
-                                       // FIXME: bug-for-bug with old parser
-                                       // Lose whitespace for no apparent reason
-                                       // Remove this after differential testing is done
-                                       if ( true ) {
-                                               // Good version
-                                               $s = substr( $s, 0, $count ) . $marker . substr( $s, $count );
-                                       } else {
-                                               // Bad version
-                                               if ( preg_match( '/^(={1,6})(.*?)(={1,6})\s*?$/', $s, $m ) ) {
-                                                       if ( $m[2] != '' ) {
-                                                               $s = $m[1] . $marker . $m[2] . $m[3];
-                                                       }
-                                               }
-                                       }
+                                       $s = substr( $s, 0, $count ) . $marker . substr( $s, $count );
                                        $this->parser->mStripState->general->setPair( $marker, '' );
                                }
                        } else {
@@ -5364,7 +5381,7 @@ class PPFrame {
                                        if ( $node->nodeType == XML_TEXT_NODE ) {
                                                $s .= $node->nodeValue;
                                        } elseif ( $node->nodeType == XML_ELEMENT_NODE ) {
-                                               $s .= $this->expand( $node, 0, $deepFlags );
+                                               $s .= $this->expand( $node, $flags );
                                        }
                                }
                        }
@@ -5374,8 +5391,8 @@ class PPFrame {
                return $s;
        }
 
-       function implodeWithFlags( $sep, $shallowFlags, $deepFlags /*, ... */ ) {
-               $args = array_slice( func_get_args(), 3 );
+       function implodeWithFlags( $sep, $flags /*, ... */ ) {
+               $args = array_slice( func_get_args(), 2 );
 
                $first = true;
                $s = '';
@@ -5389,7 +5406,7 @@ class PPFrame {
                                } else {
                                        $s .= $sep;
                                }
-                               $s .= $this->expand( $node, $shallowFlags, $deepFlags );
+                               $s .= $this->expand( $node, $flags );
                        }
                }
                return $s;
@@ -5397,10 +5414,26 @@ class PPFrame {
 
        function implode( $sep /*, ... */ ) {
                $args = func_get_args();
-               $args = array_merge( array_slice( $args, 0, 1 ), array( 0, 0 ), array_slice( $args, 1 ) );
+               $args = array_merge( array_slice( $args, 0, 1 ), array( 0 ), array_slice( $args, 1 ) );
                return call_user_func_array( array( $this, 'implodeWithFlags' ), $args );
        }
 
+       /**
+        * Split an <arg> or <template> node into a three-element array: 
+        *    DOMNode name, string index and DOMNode value
+        */
+       function splitBraceNode( $node ) {
+               $xpath = new DOMXPath( $arg->ownerDocument );
+               $names = $xpath->query( 'name', $node );
+               $values = $xpath->query( 'value', $node );
+               if ( !$names->length || !$values->length ) {
+                       throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
+               }
+               $name = $names->item( 0 );
+               $index = $name->getAttribute( 'index' );
+               return array( $name, $index, $values->item( 0 ) );
+       }
+
        function __toString() {
                return 'frame{}';
        }