Added missing search keyword highlighting
authorMarcel Werk <burntime@woltlab.com>
Fri, 19 Aug 2016 17:02:13 +0000 (19:02 +0200)
committerMarcel Werk <burntime@woltlab.com>
Fri, 19 Aug 2016 17:02:21 +0000 (19:02 +0200)
wcfsetup/install/files/lib/system/bbcode/KeywordHighlighter.class.php
wcfsetup/install/files/lib/system/html/output/HtmlOutputProcessor.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeProcessor.class.php

index d4df438321cc1ffe8bb46e7a1dc2aa4307a3c288..086ccede2eedb8e0b5906aa80e123169b4f54767 100644 (file)
@@ -135,4 +135,13 @@ class KeywordHighlighter extends SingletonFactory {
                $keywordPattern = str_replace('\*', '\w*', $keywordPattern);
                return preg_replace('+(?<!&|&\w{1}|&\w{2}|&\w{3}|&\w{4}|&\w{5}|&\w{6})'.$keywordPattern.'(?![^<]*>)+i', '<span class="highlight">\\1</span>', $text);
        }
+       
+       /**
+        * Returns search keywords
+        * 
+        * @return      string[]
+        */
+       public function getKeywords() {
+               return $this->keywords;
+       }
 }
index 6e28572b9f6d75cc8a9f3bd95faf288b064389df..fdc0865a5162109dd2227241a63123bb6d6db6ab 100644 (file)
@@ -29,14 +29,16 @@ class HtmlOutputProcessor extends AbstractHtmlProcessor {
        /**
         * Processes the input html string.
         *
-        * @param       string          $html           html string
-        * @param       string          $objectType     object type identifier
-        * @param       integer         $objectID       object id
+        * @param       string          $html                           html string
+        * @param       string          $objectType                     object type identifier
+        * @param       integer         $objectID                       object id
+        * @param       boolean         $doKeywordHighlighting                                   
         */
-       public function process($html, $objectType, $objectID) {
+       public function process($html, $objectType, $objectID, $doKeywordHighlighting = true) {
                $this->setContext($objectType, $objectID);
                
                $this->getHtmlOutputNodeProcessor()->setOutputType($this->outputType);
+               $this->getHtmlOutputNodeProcessor()->enableKeywordHighlighting($doKeywordHighlighting);
                $this->getHtmlOutputNodeProcessor()->load($this, $html);
                $this->getHtmlOutputNodeProcessor()->process();
        }
index c3402703c2dbe043c98ffa6c4255572b30d621ed..4379be102d041e1e836bd6701c7d291532509b53 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 namespace wcf\system\html\output\node;
+use wcf\system\bbcode\HtmlBBCodeParser;
+use wcf\system\bbcode\KeywordHighlighter;
 use wcf\system\html\node\AbstractHtmlNodeProcessor;
 use wcf\system\html\node\IHtmlNode;
 use wcf\util\DOMUtil;
@@ -26,6 +28,24 @@ class HtmlOutputNodeProcessor extends AbstractHtmlNodeProcessor {
         */
        protected $outputType = 'text/html';
        
+       /**
+        * enables keyword highlighting
+        * @var boolean
+        */
+       protected $keywordHighlighting = true;
+       
+       /**
+        * @var string[]
+        */
+       protected $sourceBBCodes = [];
+       
+       /**
+        * HtmlOutputNodeProcessor constructor.
+        */
+       public function __construct() {
+               $this->sourceBBCodes = HtmlBBCodeParser::getInstance()->getSourceBBCodes();
+       }
+       
        /**
         * Sets the desired output type.
         * 
@@ -39,6 +59,9 @@ class HtmlOutputNodeProcessor extends AbstractHtmlNodeProcessor {
         * @inheritDoc
         */
        public function process() {
+               // highlight keywords
+               $this->highlightKeywords();
+               
                $this->invokeHtmlNode(new HtmlOutputNodeWoltlabMetacode());
                
                // dynamic node handlers
@@ -125,6 +148,81 @@ class HtmlOutputNodeProcessor extends AbstractHtmlNodeProcessor {
                return $html;
        }
        
+       /**
+        * Enables the keyword highlighting.
+        * 
+        * @param       boolean         $enable
+        */
+       public function enableKeywordHighlighting($enable = true) {
+               $this->keywordHighlighting = $enable;
+       }
+       
+       /**
+        * Executes the keyword highlighting.
+        */
+       protected function highlightKeywords() {
+               if (!$this->keywordHighlighting) return;
+               if (!count(KeywordHighlighter::getInstance()->getKeywords())) return;
+               $keywordPattern = '('.implode('|', KeywordHighlighter::getInstance()->getKeywords()).')';
+               
+               $nodes = [];
+               foreach ($this->getXPath()->query('//text()') as $node) {
+                       $value = StringUtil::trim($node->textContent);
+                       if (empty($value)) {
+                               // skip empty nodes
+                               continue;
+                       }
+                       
+                       // check if node is within a code element or link
+                       if ($this->hasCodeParent($node)) {
+                               continue;
+                       }
+                       
+                       $nodes[] = $node;
+               }
+               foreach ($nodes as $node) {
+                       $split = preg_split('+'.$keywordPattern.'+', $node->textContent, -1, PREG_SPLIT_DELIM_CAPTURE);
+                       if (count($split) == 1) return;
+                       
+                       for ($i = 0; $i < count($split); $i++) {
+                               if ($i % 2 == 0) { // text
+                                       $node->parentNode->insertBefore($node->ownerDocument->createTextNode($split[$i]), $node);
+                               }
+                               else { // match
+                                       $element = $node->ownerDocument->createElement('span');
+                                       $element->setAttribute('class', 'highlight');
+                                       $element->appendChild($node->ownerDocument->createTextNode($split[$i]));
+                                       $node->parentNode->insertBefore($element, $node);
+                               }
+                       }
+                       
+                       DOMUtil::removeNode($node);
+               }
+       }
+       
+       /**
+        * Returns true if text node is inside a code element, suppresing any
+        * auto-detection of content.
+        *
+        * @param       \DOMText        $text           text node
+        * @return      boolean         true if text node is inside a code element
+        */
+       protected function hasCodeParent(\DOMText $text) {
+               $parent = $text;
+               /** @var \DOMElement $parent */
+               while ($parent = $parent->parentNode) {
+                       $nodeName = $parent->nodeName;
+                       if ($nodeName === 'code' || $nodeName === 'kbd' || $nodeName === 'pre') {
+                               return true;
+                       }
+                       else if ($nodeName === 'woltlab-metacode' && in_array($parent->getAttribute('data-name'), $this->sourceBBCodes)) {
+                               return true;
+                       }
+               }
+               
+               return false;
+       }
+       
        /**
         * @inheritDoc
         */