@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 195 lines 4.9 kB view raw
1<?php 2 3final class PhabricatorSearchResultView extends AphrontView { 4 5 private $handle; 6 private $object; 7 private $tokens; 8 9 public function setHandle(PhabricatorObjectHandle $handle) { 10 $this->handle = $handle; 11 return $this; 12 } 13 14 /** 15 * @param array<PhabricatorFulltextToken> $tokens 16 * @return $this 17 */ 18 public function setTokens(array $tokens) { 19 assert_instances_of($tokens, PhabricatorFulltextToken::class); 20 $this->tokens = $tokens; 21 return $this; 22 } 23 24 public function setObject($object) { 25 $this->object = $object; 26 return $this; 27 } 28 29 /** 30 * Render a search result item 31 * 32 * @return PHUIObjectItemView|null 33 */ 34 public function render() { 35 $handle = $this->handle; 36 if (!$handle->isComplete()) { 37 return null; 38 } 39 40 require_celerity_resource('phabricator-search-results-css'); 41 42 $type_name = nonempty($handle->getTypeName(), pht('Document')); 43 44 $raw_title = $handle->getFullName(); 45 $title = $this->emboldenQuery($raw_title); 46 47 $item = id(new PHUIObjectItemView()) 48 ->setHeader($title) 49 ->setTitleText($raw_title) 50 ->setHref($handle->getURI()) 51 ->setImageURI($handle->getImageURI()) 52 ->addAttribute($type_name); 53 54 if ($handle->getStatus() == PhabricatorObjectHandle::STATUS_CLOSED) { 55 $item->setDisabled(true); 56 $item->addAttribute(pht('Closed')); 57 } 58 59 return $item; 60 } 61 62 63 /** 64 * Find the words which are part of the query string, and bold them in a 65 * result string. This makes it easier for users to see why a result 66 * matched their query. 67 */ 68 private function emboldenQuery($str) { 69 $tokens = $this->tokens; 70 71 if (!$tokens) { 72 return $str; 73 } 74 75 if (count($tokens) > 16) { 76 return $str; 77 } 78 79 if (!strlen($str)) { 80 return $str; 81 } 82 83 if (strlen($str) > 2048) { 84 return $str; 85 } 86 87 $patterns = array(); 88 foreach ($tokens as $token) { 89 $raw_token = $token->getToken(); 90 $operator = $raw_token->getOperator(); 91 92 $value = $raw_token->getValue(); 93 94 switch ($operator) { 95 case PhutilSearchQueryCompiler::OPERATOR_SUBSTRING: 96 case PhutilSearchQueryCompiler::OPERATOR_EXACT: 97 $patterns[] = '(('.preg_quote($value).'))ui'; 98 break; 99 case PhutilSearchQueryCompiler::OPERATOR_AND: 100 $patterns[] = '((?<=\W|^)('.preg_quote($value).')(?=\W|\z))ui'; 101 break; 102 default: 103 // Don't highlight anything else, particularly "NOT". 104 break; 105 } 106 } 107 108 // Find all matches for all query terms in the document title, then reduce 109 // them to a map from offsets to highlighted sequence lengths. If two terms 110 // match at the same position, we choose the longer one. 111 $all_matches = array(); 112 foreach ($patterns as $pattern) { 113 $matches = null; 114 $ok = preg_match_all( 115 $pattern, 116 $str, 117 $matches, 118 PREG_OFFSET_CAPTURE); 119 if (!$ok) { 120 continue; 121 } 122 123 foreach ($matches[1] as $match) { 124 $match_text = $match[0]; 125 $match_offset = $match[1]; 126 127 if (!isset($all_matches[$match_offset])) { 128 $all_matches[$match_offset] = 0; 129 } 130 131 $all_matches[$match_offset] = max( 132 $all_matches[$match_offset], 133 strlen($match_text)); 134 } 135 } 136 137 // Go through the string one display glyph at a time. If a glyph starts 138 // on a highlighted byte position, turn on highlighting for the number 139 // of matching bytes. If a query searches for "e" and the document contains 140 // an "e" followed by a bunch of combining marks, this will correctly 141 // highlight the entire glyph. 142 $parts = array(); 143 $highlight = 0; 144 $offset = 0; 145 foreach (phutil_utf8v_combined($str) as $character) { 146 $length = strlen($character); 147 148 if (isset($all_matches[$offset])) { 149 $highlight = $all_matches[$offset]; 150 } 151 152 if ($highlight > 0) { 153 $is_highlighted = true; 154 $highlight -= $length; 155 } else { 156 $is_highlighted = false; 157 } 158 159 $parts[] = array( 160 'text' => $character, 161 'highlighted' => $is_highlighted, 162 ); 163 164 $offset += $length; 165 } 166 167 // Combine all the sequences together so we aren't emitting a tag around 168 // every individual character. 169 $last = null; 170 foreach ($parts as $key => $part) { 171 if ($last !== null) { 172 if ($part['highlighted'] == $parts[$last]['highlighted']) { 173 $parts[$last]['text'] .= $part['text']; 174 unset($parts[$key]); 175 continue; 176 } 177 } 178 179 $last = $key; 180 } 181 182 // Finally, add tags. 183 $result = array(); 184 foreach ($parts as $part) { 185 if ($part['highlighted']) { 186 $result[] = phutil_tag('strong', array(), $part['text']); 187 } else { 188 $result[] = $part['text']; 189 } 190 } 191 192 return $result; 193 } 194 195}