refineHTMLValue( $data[$this->field] ); return "
".$this->processText( $value, $keylink)."
"; } /** * Remove excessive spaces and script tags from the string passed * @param String value * @return String */ protected function refineHTMLValue($value) { $value = preg_replace('/\s+/', ' ', $value); return $this->stripScriptTags($value); } /** * Get the string obtained from the string passed * by removing all script tags * @param String value * @return String */ protected function stripScriptTags($value) { return preg_replace('/<\s*script\s*(([^">]+="[^"]+"\s*)|'."([^'>]+='[^']+'\s*)".')*>.*<\s*\/script\s*>/', '', $value); } /** * Check if the text needs truncating * @param String value The field's content * @param Number cNumberOfChars The number of chars up to truncation * @return Boolean */ protected function textNeedsTruncating($value, $cNumberOfChars) { return !$this->isUsedForFilter && !$this->container->fullText && $cNumberOfChars > 0 && runner_strlen($value) > $cNumberOfChars * 1.2; } /** * Form the the string with the search word highlighted * @param String value The field's content * @param Array highlightData * @return string */ public function getValueHighlighted($value, $highlightData) { $searchOpt = $highlightData['searchOpt']; if( $searchOpt == 'Equals' ) return $this->addHighlightingSpan($value); $flags = $this->useUTF8 ? "iu" : "i"; $prefix = ($searchOpt == 'Starts with') ? "^" : ""; //ungreedy tag pattern $tagPattern = "/(<[^=>]+\s*(?:(?:[^=>]+=\s*'[^']+'\s*)|".'(?:[^=>]+=\s*"[^"]+"\s*)'.")*>)/iU"; foreach($highlightData['searchWords'] as $searchWord) { $searchWordParts = preg_split($tagPattern, $searchWord); if( count($searchWordParts) == 1 ) { $res = ""; $highlighted = false; //remove tag fragments $newSearchWord = preg_replace("/^.*>|<.*$/U", '', $searchWord); $pattern = '/'.$prefix.'('.preg_quote($newSearchWord,"/").')/'.$flags; //the search word doesn't contain any tags $valueArr = $this->getSplitStringWithCapturedDelimiters($tagPattern, $value); foreach($valueArr as $item) { if( !strlen($item) ) continue; //It's a tag or the tag inside a tag's attribute was matched if( $item[0] == '<' || $item[ strlen($item) - 1 ] == '>' || $highlighted ) { $res.= $item; continue; } if( !$this->hasHTMLEntities($item) ) $replacedItem = preg_replace($pattern, $this->addHighlightingSpan('$1'), $item); else $replacedItem = $this->highlightValueWithHTMLEntities($item, $pattern); if( $searchOpt == 'Starts with' && $item != $replacedItem ) $highlighted = true; $res.= $replacedItem; } $value = $res; continue; } //the search word contains tags foreach($searchWordParts as $item) { if( trim($item) ) { if($item[0] != '<' && $item[ strlen($item) - 1 ] != '>' ) { //remove tag fragments $newItem = preg_replace("/^.*>|<.*$/", '', $item); $itemPattern = preg_quote($newItem, "/"); $pattern = '/(>[^>]*)('.$itemPattern.')([^<]*<)|^([^<>]*)('.$itemPattern.')(<)|(>)('.$itemPattern.')([^<>]*$)/U'; //$patterns = array('/(>[^>]*)('.$itemPattern.')([^<]*<)/U' , '/^([^<>]*)('.$itemPattern.')(<)/', '/(>)('.$itemPattern.')([^<>]*$)/'); $value = preg_replace($pattern, "$1".$this->addHighlightingSpan("$2")."$3", $value); } } } } return $value; } /** * Remove from the value the current tag defined by its start * and end positions in the value string * @param &String value * @param Number tagStart * @param Number tagEnd */ protected function removeCurrentTag(&$value, $tagStart, $tagEnd) { if( $tagStart > 0 ) $value = substr($value, 0, $tagStart).substr($value, $tagEnd + 1); else $value = substr($value, $tagEnd + 1); } /** * Basing on the data passed method returns the data array * that contains the 'removeTag' property indicating * If it's necessary to remove the current tag from the field's content * and 'tags' array containing the tag names that should be closed then * @param Array tags * @param String tagName * @param Boolean closingTag * @return Array */ protected function processTagsToClose($tags, $tagName, $closingTag) { $data = array('removeTag'=> false, 'tags'=> $tags); if( $this->isTagSingleTon($tagName) ) return $data; if( !$closingTag ) { $data['tags'][] = $tagName; return $data; } $keys = array_keys($tags, $tagName); if( !count($keys) ) $data['removeTag'] = true; else //pop all elements upto the preceeding element of the openin tag $data['tags'] = array_slice( $tags, 0, -1 ); return $data; } /** * Form the data array that contains a 'isHTMLEntity' status (if the HTML entity * is on the 'pos' position in the string passed) and It could contain the * length of the html entity('htmlEntityLength') and the number of characters * It represents('entityLength') * @param Number pos * @param String string * @return Array */ protected function isHTMLEntity($pos, $string) { $endityEndPos = strpos($string, ";", $pos); if( $endityEndPos === FALSE ) return array('isHTMLEntity'=> false); $suspect = substr( $string, $pos, $endityEndPos - $pos + 1 ); $data = getHTMLEntityData( $suspect ); $data['htmlEntityLength'] = strlen( $suspect ); return $data; } /** * Check if a comment startes from the position 'pos' in a string passed * @param Number pos * @param String string * @return Array */ protected function isComment($pos, $string) { if( substr($string, $pos, 4) != "", $pos); if( $commentClosePos !== FALSE ) return array('status'=> true, "commentLength"=> $commentClosePos - $pos + 3); return array('status'=> false); } /** * Get an array containing the corrected field's content and * the flas if the field's content was truncated * @param String value The field's content * @param Number cNumberOfChars This number defines set of chars in the HTML value's text nodes that should be included in the processed value * return Array The array containing the processed value and the flag "isTruncated" indicating if the content is truncated */ protected function getPocessedHTMLValueData($value, $cNumberOfChars) { $tag = $closingTag = $attrValue = $tagNameReady = $waitForAttrValue = $skipTagContent =false; $attrCloseMark = ""; $tagName = ""; $tags = array(); $notContentPositions = array(); $i = $j = 0; $skipTagStart = -1; //traversing the html string while( $i < strlen($value) && $j < $cNumberOfChars ) { if( !$tag && $value[$i] == '<') { $data = $this->isComment($i, $value); if( $data['status'] ) { $notContentPositions[] = array(0=> $i, 1=> $i + $data['commentLength']); //skip the comment $i = $i + $data['commentLength']; } else { $tagStart = $i; $tagNameReady = false; $tag = true; $i = $i + 1;; } continue; } if( $tag && $attrValue && ($attrCloseMark == "" || $attrCloseMark == " ") && $value[$i] == '>' ) $attrValue = false; if( $tag && !$attrValue && $value[$i] == '>' ) { //presuppose that invisible tags(style) could not be enclosed if( $this->isInvisibleTag($tagName) ) $skipTagContent = !$closingTag; if( $skipTagContent ) $skipTagStart = $i; else if( $skipTagStart != -1 ) { $notContentPositions[] = array(0=> $skipTagStart, 1=> $i); $skipTagStart = -1; } $data = $this->processTagsToClose( $tags, $tagName, $closingTag); $tags = $data['tags']; if( $data['removeTag'] ) { //remove the single closing tag $this->removeCurrentTag($value, $tagStart, $i); //update the itertator value $i = $tagStart; } else { $notContentPositions[] = array(0=> $tagStart, 1=> $i); $i = $i + 1;; } $tag = $closingTag = false; $tagNameReady = true; $tagName = ""; continue; } if( $tag && !$tagNameReady ) { if( $value[$i] == "/" ) $closingTag = true; else if( $value[$i] == " " && $tagName != "" ) $tagNameReady = true; else if( $value[$i] != " " ) //form the tag's name $tagName.= $value[$i]; $i = $i + 1; continue; } if( $tag && $tagNameReady ) { //process the attributes inside the tag if( $value[$i] == "=" && !$attrValue ) $waitForAttrValue = true; else if( $waitForAttrValue && $value[$i] != " " ) { if( $value[$i] == "'" || $value[$i] == '"' ) $attrCloseMark = $value[$i]; $waitForAttrValue = false; $attrValue = true; } else if( $attrValue && $value[$i] == $attrCloseMark ) { $attrValue = false; $attrCloseMark = " "; } $i = $i + 1; continue; } if( $skipTagContent ) { $i = $i + 1; continue; } if( $value[$i] == '&' ) { $data = $this->isHTMLEntity($i, $value); if( $data['isHTMLEntity'] ) { //skip the html-entity $i = $i + $data['htmlEntityLength']; $j = $j + $data['entityLength']; continue; } } //echo $value[$i]." ".$i."
"; if( $this->useUTF8 ) { $ordord = ord( $value[$i] ); if (0xC0 == (0xC0 & $ordord)) // 11000000 $i = $i + 1; else if (0xE0 == (0xE0 & $ordord)) // 11100000 $i = $i + 2; else if (0xF0 == (0xF0 & $ordord)) // 11110000 $i = $i + 3; else if (0xF8 == (0xF8 & $ordord)) // 11111000 $i = $i + 4; else if(0xFC == (0xFC & $ordord)) // 11111100 $i = $i + 5; } $i = $i + 1; $j = $j + 1; } $truncatedValue = substr($value, 0, $i); $tags = array_reverse( $tags ); foreach($tags as $tag) { $truncatedValue.= ""; } $notContentPositions[] = array(0=> $i, 1=> strlen($truncatedValue) - 1); //echo "
".runner_htmlspecialchars( $truncatedValue ); return array("value"=> $truncatedValue, "isTruncated"=> $i < strlen($value), "notContentPositions"=> $notContentPositions, "truncLength"=> $i); } /** * Get the More link following the truncated and highlighted field's content * @param String value The field's content * @param Number cNumberOfChars * @param String keylink * @param Boolean isLookup An indicator showing if this is a lookup list page control. * It's alway equal to false for the view as HTML field * @return String */ protected function getShorteningTextAndMoreLink($value, $cNumberOfChars, $keylink, $isLookup) { global $strTableName; $data = $this->getPocessedHTMLValueData($value, $cNumberOfChars); $isTruncated = $data["isTruncated"]; $processedValue = $data["value"]; if( $this->searchHighlight ) { if( $isTruncated ) $processedValue = $this->highlightTruncatedBeforeMore($value, $processedValue, $cNumberOfChars, $data["truncLength"]); else $processedValue = $this->highlightSearchWord($processedValue, false, ""); } if( $isTruncated ) { $params = 'pagetype='.$this->container->pSet->_viewPage.'&table='.GetTableURL($strTableName).'&field='.rawurlencode($this->field).$keylink; $link = ' '.mlang_message("MORE").' ...'; $processedValue.= $link; } return $processedValue; } /** * Format the string before the "More ..." link and highlight a search word depending on the search option's value. * @param String value The raw field's content * @param String truncatedValue The truncated field's content * @param Number cNumberOfChars * @param Number truncLength The length of the truncated field's content without the final closing tags * @return string */ protected function highlightTruncatedBeforeMore($value, $truncatedValue, $cNumberOfChars, $truncLength) { $highlightData = $this->searchClauseObj->getSearchHighlightingData( $this->field, $value, false, array() ); if( !$highlightData ) return $truncatedValue; $data = $this->getPocessedHTMLValueData( $value, strlen($value) ); $processedValue = $data['value']; $firstSearchWordData = $this->getFirstSearchWordData($highlightData['searchWords'], $data['notContentPositions'], $processedValue); $firstPos = $firstSearchWordData["position"]; $hasTags = $firstSearchWordData["hasTags"]; $firstSearchWord = $firstSearchWordData["searchWord"]; if( $firstPos === FALSE ) return $truncatedValue; if( $firstPos + strlen( $firstSearchWord ) <= $truncLength ) return $this->getValueHighlighted($truncatedValue, $highlightData); if( $firstPos <= $truncLength ) { $truncatedUnicodeLength = runner_strlen( substr($value, 0, $truncLength) ); $truncatedWithSearchWordUnicodeLength = runner_strlen( substr($value, 0, $firstPos + strlen($firstSearchWord))); $data = $this->getPocessedHTMLValueData($value, $cNumberOfChars + $truncatedWithSearchWordUnicodeLength - $truncatedUnicodeLength); return $this->getValueHighlighted($data['value'], $highlightData); } return $this->getValueHighlighted($truncatedValue, $highlightData)." <...> ".$this->getSearchWordHighlighted($hasTags, $firstSearchWord); } /** * Hightlight the search word passed * @param Boolean hasTags * @param String searchWord * @return Boolean */ protected function getSearchWordHighlighted($hasTags, $searchWord) { if( !$hasTags ) return $this->addHighlightingSpan($searchWord); $highligntedSearchWord = ""; $tagPattern = "/(<[^=>]+\s*(?:(?:[^=>]+=\s*'[^']+'\s*)|".'(?:[^=>]+=\s*"[^"]+"\s*)'.")*>)/iU"; $searchWordArr = $this->getSplitStringWithCapturedDelimiters($tagPattern, $searchWord); foreach($searchWordArr as $item) { if( trim($item) && $item[0] != '<' && $item[ strlen($item) - 1 ] != '>' ) { //remove tag fragments $newItem = preg_replace("/^.*>|<.*$/", '', $item); $itemPattern = preg_quote($newItem,"/"); $newItem = preg_replace('/('.$itemPattern.')/', $this->addHighlightingSpan('$1'), $newItem); } $highligntedSearchWord.= $newItem; } return $highligntedSearchWord; } /** * Get the first search word and its position in the HTML value content * @param String searchWords * @param Array notContentPositions * @param String value * @return Array */ protected function getFirstSearchWordData($searchWords, $notContentPositions, $value) { $hasTags = false; $firstSearchWord = ""; $firstPos = strlen($value); $tagPattern = "/(<[^=>]+\s*(?:(?:[^=>]+=\s*'[^']+'\s*)|".'(?:[^=>]+=\s*"[^"]+"\s*)'.")*>)/iU"; foreach($searchWords as $searchWord) { $pos = strpos($value, $searchWord); $hasTags = true; if( !preg_match($tagPattern, $searchWord, $matches) ) { while( $this->notInContent($pos, $notContentPositions) && $pos !== FALSE ) { $pos = strpos($value, $searchWord, $pos + 1); } $hasTags = false; } if( $pos !== FALSE && $pos < $firstPos ) { $firstPos = $pos; $firstSearchWord = $searchWord; } } return array("position"=> $firstPos != strlen($value) ? $firstPos : FALSE, "searchWord"=> $firstSearchWord, "hasTags"=> $hasTags); } /** * Check if the position 'pos' is inside the HTML non content's parts (tags, * comments) basing on the array containing the non-content * start-end positions pairs (eg a tag's start and end positions) * @param Number pos * @param Array notContentPositions * @return Boolean */ protected function notInContent($pos, $notContentPositions) { foreach($notContentPositions as $positions) { if( $positions[0] <= $pos && $pos <= $positions[1] ) return true; } return false; } /** * Get the shortening HTML value * @param String value The field's content * @param Number cNumberOfChars * @return String */ protected function getShorteningText($value, $cNumberOfChars) { $data = $this->getPocessedHTMLValueData($value, $cNumberOfChars); if( $data["isTruncated"] ) return $data["value"]." ..."; return $data["value"]; } /** * Get the corrected and highlighted HTML value * @param String value * @return String */ protected function getText($value) { $data = $this->getPocessedHTMLValueData( $value, strlen($value) ); $value = $data['value']; if( $this->searchHighlight ) $value = $this->highlightSearchWord($value, false, ""); return $value; } /** * Check if the tag has invisible content * @param String tagName * @return Boolean */ protected function isInvisibleTag($tagName) { return $tagName == "style"; } /** * Check if the tag is a singleton * @param String tagName * @return Boolean */ protected function isTagSingleTon($tagName) { $singleTonTags = array("base", "br", "col", "command", "embed", "hr", "img", "input", "link", "meta", "param", "source"); if( in_array( $tagName, $singleTonTags ) ) return true; return false; } } ?>