Fixed handling of source bbcodes
authorAlexander Ebert <ebert@woltlab.com>
Mon, 18 Jul 2016 22:55:59 +0000 (00:55 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Mon, 18 Jul 2016 22:56:06 +0000 (00:56 +0200)
wcfsetup/install/files/lib/system/bbcode/HtmlBBCodeParser.class.php

index c5b1ba57cccbc7f401c4c5a7081790fcc794cd63..f9a582daeaceed8c8e6e8eeddafad50d676d9ce2 100644 (file)
@@ -46,11 +46,170 @@ class HtmlBBCodeParser extends BBCodeParser {
                // tags that will eventually be removed within the marker processor
                $this->buildXMLStructure();
                
+               $this->handleSourceBBCodes();
+               
                $this->buildParsedString();
                
                return $this->parsedText;
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function buildXMLStructure() {
+               // stack for open tags
+               $openTagStack = $openTagDataStack = [];
+               $newTagArray = [];
+               $newTextArray = [];
+               
+               $i = -1;
+               foreach ($this->tagArray as $i => $tag) {
+                       if ($tag['closing']) {
+                               // closing tag
+                               if (in_array($tag['name'], $openTagStack) && $this->isAllowed($openTagStack, $tag['name'], true)) {
+                                       // close unclosed tags
+                                       while (($previousTag = end($openTagStack)) != $tag['name']) {
+                                               $nextIndex = count($newTagArray);
+                                               
+                                               // mark as invalid and do not flag as opened tag
+                                               $newTag = $this->buildTag('[/'.end($openTagStack).']');
+                                               $newTag['invalid'] = true;
+                                               
+                                               $newTagArray[$nextIndex] = $newTag;
+                                               if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
+                                               $newTextArray[$nextIndex] .= $this->textArray[$i];
+                                               $this->textArray[$i] = '';
+                                               array_pop($openTagStack);
+                                               array_pop($openTagDataStack);
+                                       }
+                                       
+                                       $nextIndex = count($newTagArray);
+                                       $newTagArray[$nextIndex] = $tag;
+                                       array_pop($openTagStack);
+                                       array_pop($openTagDataStack);
+                                       if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
+                                       $newTextArray[$nextIndex] .= $this->textArray[$i];
+                               }
+                               else {
+                                       // no such tag open
+                                       // handle as plain text
+                                       $this->textArray[$i] .= $tag['source'];
+                                       $last = count($newTagArray);
+                                       if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
+                                       $newTextArray[$last] .= $this->textArray[$i];
+                               }
+                       }
+                       else {
+                               // opening tag
+                               if ($this->isAllowed($openTagStack, $tag['name']) && $this->isValidTag($tag)) {
+                                       $openTagStack[] = $tag['name'];
+                                       $openTagDataStack[] = $tag;
+                                       $nextIndex = count($newTagArray);
+                                       $newTagArray[$nextIndex] = $tag;
+                                       if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
+                                       $newTextArray[$nextIndex] .= $this->textArray[$i];
+                               }
+                               else {
+                                       // tag not allowed
+                                       $this->textArray[$i] .= $tag['source'];
+                                       $last = count($newTagArray);
+                                       if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
+                                       $newTextArray[$last] .= $this->textArray[$i];
+                               }
+                       }
+               }
+               
+               $last = count($newTagArray);
+               if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
+               $newTextArray[$last] .= $this->textArray[$i + 1];
+               
+               // close unclosed open tags
+               while (end($openTagStack)) {
+                       $nextIndex = count($newTagArray);
+                       
+                       // mark as invalid
+                       $newTag = $this->buildTag('[/'.end($openTagStack).']');
+                       $newTag['invalid'] = true;
+                       
+                       $newTagArray[$nextIndex] = $newTag;
+                       if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
+                       array_pop($openTagStack);
+                       array_pop($openTagDataStack);
+               }
+               
+               $this->tagArray = $newTagArray;
+               $this->textArray = $newTextArray;
+       }
+       
+       /**
+        * Flags bbcodes inside code bbcodes for reversal, turning them back
+        * into their source state (= textual representation).
+        */
+       protected function handleSourceBBCodes() {
+               $sourceBBCodes = $this->getSourceBBCodes();
+               
+               $inCode = '';
+               $openTagStack = [];
+               
+               for ($i = 0, $length = count($this->tagArray); $i < $length; $i++) {
+                       $tag = $this->tagArray[$i];
+                       
+                       if (!empty($tag['invalid'])) {
+                               continue;
+                       }
+                       
+                       $name = $tag['name'];
+                       
+                       if ($tag['closing']) {
+                               if ($inCode) {
+                                       // matches opening code tag
+                                       if ($inCode === $name) {
+                                               $inCode = '';
+                                               array_pop($openTagStack);
+                                       }
+                                       else {
+                                               // unrelated tag, flag as invalid
+                                               $this->tagArray[$i]['inCode'] = true;
+                                       }
+                                       
+                                       continue;
+                               }
+                               
+                               array_pop($openTagStack);
+                       }
+                       else {
+                               if ($inCode) {
+                                       // inside code block, flag as invalid
+                                       $this->tagArray[$i]['inCode'] = true;
+                                       continue;
+                               }
+                               
+                               // starts a new code block
+                               if (in_array($name, $sourceBBCodes)) {
+                                       // look ahead to see if there is a valid closing tag
+                                       $hasClosingTag = false;
+                                       for ($j = $i + 1; $j < $length; $j++) {
+                                               if ($this->tagArray[$j]['name'] === $name && empty($this->tagArray[$j]['invalid'])) {
+                                                       $hasClosingTag = true;
+                                                       break;
+                                               }
+                                       }
+                                       
+                                       if ($hasClosingTag) {
+                                               $inCode = $name;
+                                       }
+                                       else {
+                                               // no closing tag, flag as invalid to avoid the
+                                               // entire content afterwards being treated as code
+                                               $this->tagArray[$i]['inCode'] = true;
+                                       }
+                               }
+                               
+                               $openTagStack[] = $name;
+                       }
+               }
+       }
+       
        /**
         * @inheritDoc
         */
@@ -74,6 +233,16 @@ class HtmlBBCodeParser extends BBCodeParser {
                        $buffer .= $this->textArray[$i];
                        
                        if ($tag['closing']) {
+                               if (!empty($tag['invalid'])) {
+                                       // drop invalid closing tag
+                                       continue;
+                               }
+                               else if (!empty($tag['inCode'])) {
+                                       // revert bbcodes inside code
+                                       $buffer .= $tag['source'];
+                                       continue;
+                               }
+                               
                                // get buffered opening tag
                                $openingTag = end($bufferedTagStack);
                                
@@ -127,6 +296,12 @@ class HtmlBBCodeParser extends BBCodeParser {
                                }
                        }
                        else {
+                               if (!empty($tag['inCode'])) {
+                                       // revert bbcodes inside code
+                                       $buffer .= $tag['source'];
+                                       continue;
+                               }
+                               
                                // opening tag
                                if ($this->needBuffering($tag)) {
                                        // start buffering
@@ -143,94 +318,6 @@ class HtmlBBCodeParser extends BBCodeParser {
                if (isset($this->textArray[$i + 1])) $this->parsedText .= $this->textArray[$i + 1];
        }
        
-       /**
-        * @inheritDoc
-        */
-       public function buildXMLStructure() {
-               // stack for open tags
-               $openTagStack = $openTagDataStack = [];
-               $newTagArray = [];
-               $newTextArray = [];
-               
-               $i = -1;
-               foreach ($this->tagArray as $i => $tag) {
-                       if ($tag['closing']) {
-                               // closing tag
-                               if (in_array($tag['name'], $openTagStack) && $this->isAllowed($openTagStack, $tag['name'], true)) {
-                                       // close unclosed tags
-                                       while (($previousTag = end($openTagStack)) != $tag['name']) {
-                                               $nextIndex = count($newTagArray);
-                                               
-                                               // mark as invalid and do not flag as opened tag
-                                               $newTag = $this->buildTag('[/'.end($openTagStack).']');
-                                               $newTag['invalid'] = true;
-                                               
-                                               $newTagArray[$nextIndex] = $newTag;
-                                               if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
-                                               $newTextArray[$nextIndex] .= $this->textArray[$i];
-                                               $this->textArray[$i] = '';
-                                               array_pop($openTagStack);
-                                               array_pop($openTagDataStack);
-                                       }
-                                       
-                                       $nextIndex = count($newTagArray);
-                                       $newTagArray[$nextIndex] = $tag;
-                                       array_pop($openTagStack);
-                                       array_pop($openTagDataStack);
-                                       if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
-                                       $newTextArray[$nextIndex] .= $this->textArray[$i];
-                               }
-                               else {
-                                       // no such tag open
-                                       // handle as plain text
-                                       $this->textArray[$i] .= $tag['source'];
-                                       $last = count($newTagArray);
-                                       if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
-                                       $newTextArray[$last] .= $this->textArray[$i];
-                               }
-                       }
-                       else {
-                               // opening tag
-                               if ($this->isAllowed($openTagStack, $tag['name']) && $this->isValidTag($tag)) {
-                                       $openTagStack[] = $tag['name'];
-                                       $openTagDataStack[] = $tag;
-                                       $nextIndex = count($newTagArray);
-                                       $newTagArray[$nextIndex] = $tag;
-                                       if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
-                                       $newTextArray[$nextIndex] .= $this->textArray[$i];
-                               }
-                               else {
-                                       // tag not allowed
-                                       $this->textArray[$i] .= $tag['source'];
-                                       $last = count($newTagArray);
-                                       if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
-                                       $newTextArray[$last] .= $this->textArray[$i];
-                               }
-                       }
-               }
-               
-               $last = count($newTagArray);
-               if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
-               $newTextArray[$last] .= $this->textArray[$i + 1];
-               
-               // close unclosed open tags
-               while (end($openTagStack)) {
-                       $nextIndex = count($newTagArray);
-                       
-                       // mark as invalid
-                       $newTag = $this->buildTag('[/'.end($openTagStack).']');
-                       $newTag['invalid'] = true;
-                       
-                       $newTagArray[$nextIndex] = $newTag;
-                       if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
-                       array_pop($openTagStack);
-                       array_pop($openTagDataStack);
-               }
-               
-               $this->tagArray = $newTagArray;
-               $this->textArray = $newTextArray;
-       }
-       
        /**
         * Builds the bbcode output.
         * 
@@ -376,11 +463,6 @@ class HtmlBBCodeParser extends BBCodeParser {
                        throw new SystemException("Tag mismatch, expected '".$name."', got '".$data['name']."'.");
                }
                
-               if (!empty($data['invalid'])) {
-                       // drop invalid closing tags
-                       return '';
-               }
-               
                return '<woltlab-metacode-marker data-uuid="' . $data['uuid'] . '" data-source="' . base64_encode($tag['source']) . '" />';
        }