Fix handling for whitespace in template arguments.
[lhc/web/wiklou.git] / includes / Parser.php
index ea02a6e..6642fd6 100644 (file)
@@ -87,9 +87,7 @@ class Parser
        var $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
        var $mInterwikiLinkHolders, $mLinkHolders;
        var $mIncludeSizes, $mPPNodeCount, $mDefaultSort;
-       var $mTplExpandCache,// empty-frame expansion cache
-           $mTemplatePath;     // stores an unsorted hash of all the templates already loaded
-                               // in this path. Used for loop detection.
+       var $mTplExpandCache; // empty-frame expansion cache
        var $mTplRedirCache, $mTplDomCache, $mHeadings;
 
        # Temporary
@@ -166,6 +164,7 @@ class Parser
                $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( 'tag',              array( 'CoreParserFunctions', 'tagObj'           ), SFH_OBJECT_ARGS );
 
                if ( $wgAllowDisplayTitle ) {
                        $this->setFunctionHook( 'displaytitle', array( 'CoreParserFunctions', 'displaytitle' ), SFH_NO_HASH );
@@ -224,7 +223,6 @@ class Parser
                $this->mUniqPrefix = "\x7fUNIQ" . Parser::getRandomString();
 
                # Clear these on every parse, bug 4549
-               $this->mTemplatePath = array();
                $this->mTplExpandCache = $this->mTplRedirCache = $this->mTplDomCache = array();
 
                $this->mShowToc = true;
@@ -3010,12 +3008,11 @@ class Parser
                                        foreach ( $parts as $partIndex => $part ) {
                                                if ( isset( $piece['eqpos'][$partIndex] ) ) {
                                                        $eqpos = $piece['eqpos'][$partIndex];
-                                                       list( $ws1, $argName, $ws2 ) = self::splitWhitespace( substr( $part, 0, $eqpos ) );
-                                                       list( $ws3, $argValue, $ws4 ) = self::splitWhitespace( substr( $part, $eqpos + 1 ) );
-                                                       $element .= "<part>$ws1<name>$argName</name>$ws2=$ws3<value>$argValue</value>$ws4</part>";
+                                                       $argName = substr( $part, 0, $eqpos );
+                                                       $argValue = substr( $part, $eqpos + 1 );
+                                                       $element .= "<part><name>$argName</name>=<value>$argValue</value></part>";
                                                } else {
-                                                       list( $ws1, $value, $ws2 ) = self::splitWhitespace( $part );
-                                                       $element .= "<part>$ws1<name index=\"$argIndex\" /><value>$value</value>$ws2</part>";
+                                                       $element .= "<part><name index=\"$argIndex\" /><value>$part</value></part>";
                                                        $argIndex++;
                                                }
                                        }
@@ -3250,7 +3247,8 @@ class Parser
                        $id = $this->mVariables->matchStartToEnd( $part1 );
                        if ( $id !== false ) {
                                $text = $this->getVariableValue( $id );
-                               $this->mOutput->mContainsOldMagic = true;
+                               if (MagicWord::getCacheTTL($id)>-1)
+                                       $this->mOutput->mContainsOldMagic = true;
                                $found = true;
                        }
                }
@@ -3275,9 +3273,6 @@ class Parser
                }
                wfProfileOut( __METHOD__.'-modifiers' );
 
-               # Save path level before recursing into functions & templates.
-               $lastPathLevel = $this->mTemplatePath;
-
                # Parser functions
                if ( !$found ) {
                        wfProfileIn( __METHOD__ . '-pfunc' );
@@ -3355,11 +3350,17 @@ class Parser
                                        $wgContLang->findVariantLink($part1, $title);
                                }
                                # Do infinite loop check
-                               if ( isset( $this->mTemplatePath[$titleText] ) ) {
+                               if ( isset( $frame->loopCheckHash[$titleText] ) ) {
                                        $found = true;
-                                       $text = "[[$part1]]" . $this->insertStripItem( '<!-- WARNING: template loop detected -->' );
+                                       $text = "<span class=\"error\">Template loop detected: [[$titleText]]</span>";
                                        wfDebug( __METHOD__.": template loop broken at '$titleText'\n" );
                                }
+                               # Do recursion depth check
+                               $limit = $this->mOptions->getMaxTemplateDepth();
+                               if ( $frame->depth >= $limit ) {
+                                       $found = true;
+                                       $text = "<span class=\"error\">Template recursion depth limit exceeded ($limit)</span>";
+                               }
                        }
                }
 
@@ -3410,8 +3411,6 @@ class Parser
                # Recover the source wikitext and return it
                if ( !$found ) {
                        $text = '{{' . $frame->implode( '|', $titleWithSpaces, $args ) . '}}';
-                       # Prune lower levels off the recursion check path
-                       $this->mTemplatePath = $lastPathLevel;
                        wfProfileOut( $fname );
                        return $text;
                }
@@ -3420,8 +3419,8 @@ class Parser
                if ( $isDOM ) {
                        # Clean up argument array
                        $newFrame = $frame->newChild( $args, $title );
-                       # Add a new element to the templace recursion path
-                       $this->mTemplatePath[$titleText] = 1;
+                       # Add a new element to the templace loop detection hashtable
+                       $newFrame->loopCheckHash[$titleText] = true;
 
                        if ( $titleText !== false && count( $newFrame->args ) == 0 ) {
                                # Expansion is eligible for the empty-frame cache
@@ -3432,6 +3431,7 @@ class Parser
                                        $this->mTplExpandCache[$titleText] = $text;
                                }
                        } else {
+                               # Uncached expansion
                                $text = $newFrame->expand( $text );
                        }
                }
@@ -3453,9 +3453,6 @@ class Parser
                        $text = "\n" . $text;
                }
                
-               # Prune lower levels off the recursion check path
-               $this->mTemplatePath = $lastPathLevel;
-
                if ( !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
                        # Error, oversize inclusion
                        $text = "[[$originalTitle]]" . 
@@ -3636,8 +3633,10 @@ class Parser
                $arg = trim( $argWithSpaces );
 
                if ( isset( $frame->args[$arg] ) ) {
+                       # Found template argument
                        $text = $frame->parent->expand( $frame->args[$arg] );
                } else if ( ( $this->ot['html'] || $this->ot['pre'] ) && $parts->length > 0 ) {
+                       # No match in frame, use the supplied default
                        $text = $frame->expand( $parts->item( 0 ) );
                }
                if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
@@ -3645,6 +3644,7 @@ class Parser
                }
 
                if ( $text === false ) {
+                       # No match anywhere
                        $text = '{{{' . $frame->implode( '|', $argWithSpaces, $parts ) . '}}}';
                }
                if ( $error !== false ) {
@@ -3661,7 +3661,8 @@ class Parser
         *
         * @param array $params Associative array of parameters:
         *     name       DOMNode for the tag name
-        *     attrText   DOMNode for unparsed text where tag attributes are thought to be
+        *     attr       DOMNode for unparsed text where tag attributes are thought to be
+        *     attributes Optional associative array of parsed attributes
         *     inner      Contents of extension element
         *     noClose    Original text did not have a close tag
         * @param PPFrame $frame
@@ -3671,15 +3672,18 @@ class Parser
                static $n = 1;
 
                $name = $frame->expand( $params['name'] );
-               $attrText = is_null( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
-               $content = is_null( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
+               $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
+               $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
 
                $marker = "{$this->mUniqPrefix}-$name-" . sprintf('%08X', $n++) . $this->mMarkerSuffix;
                
                if ( $this->ot['html'] ) {
                        $name = strtolower( $name );
 
-                       $params = Sanitizer::decodeTagAttributes( $attrText );
+                       $attributes = Sanitizer::decodeTagAttributes( $attrText );
+                       if ( isset( $params['attributes'] ) ) {
+                               $attributes = $attributes + $params['attributes'];
+                       }
                        switch ( $name ) {
                                case 'html':
                                        if( $wgRawHtml ) {
@@ -3693,15 +3697,15 @@ class Parser
                                        break;
                                case 'math':
                                        $output = $wgContLang->armourMath(
-                                               MathRenderer::renderMath( $content, $params ) );
+                                               MathRenderer::renderMath( $content, $attributes ) );
                                        break;
                                case 'gallery':
-                                       $output = $this->renderImageGallery( $content, $params );
+                                       $output = $this->renderImageGallery( $content, $attributes );
                                        break;
                                default:
                                        if( isset( $this->mTagHooks[$name] ) ) {
                                                $output = call_user_func_array( $this->mTagHooks[$name],
-                                                       array( $content, $params, $this ) );
+                                                       array( $content, $attributes, $this ) );
                                        } else {
                                                throw new MWException( "Invalid call hook $name" );
                                        }
@@ -4350,8 +4354,6 @@ class Parser
         *   found                     The text returned is valid, stop processing the template. This
         *                             is on by default.
         *   nowiki                    Wiki markup in the return value should be escaped
-        *   noparse                   Unsafe HTML tags should not be stripped, etc.
-        *   noargs                    Don't replace triple-brace arguments in the return value
         *   isHTML                    The returned text is HTML, armour it against wikitext transformation
         *
         * @public
@@ -5321,6 +5323,17 @@ class PPFrame {
        var $parser, $title;
        var $titleCache;
 
+       /**
+        * Hashtable listing templates which are disallowed for expansion in this frame,
+        * having been encountered previously in parent frames.
+        */
+       var $loopCheckHash;
+
+       /**
+        * Recursion depth of this frame, top = 0
+        */
+       var $depth;
+
        const NO_ARGS = 1;
        const NO_TEMPLATES = 2;
        const STRIP_COMMENTS = 4;
@@ -5337,6 +5350,8 @@ class PPFrame {
                $this->parser = $parser;
                $this->title = $parser->mTitle;
                $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false );
+               $this->loopCheckHash = array();
+               $this->depth = 0;
        }
 
        /**
@@ -5580,8 +5595,7 @@ class PPFrame {
  * Expansion frame with template arguments
  */
 class PPTemplateFrame extends PPFrame {
-       var $parser, $args, $parent;
-       var $titleCache;
+       var $args, $parent;
 
        function __construct( $parser, $parent = false, $args = array(), $title = false ) {
                $this->parser = $parser;
@@ -5590,6 +5604,8 @@ class PPTemplateFrame extends PPFrame {
                $this->title = $title;
                $this->titleCache = $parent->titleCache;
                $this->titleCache[] = $title ? $title->getPrefixedDBkey() : false;
+               $this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
+               $this->depth = $parent->depth + 1;
        }
 
        function __toString() {