@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
at upstream/main 399 lines 9.8 kB view raw
1<?php 2 3/** 4 * @extends PhabricatorCursorPagedPolicyAwareQuery<PhabricatorPaste> 5 */ 6final class PhabricatorPasteQuery 7 extends PhabricatorCursorPagedPolicyAwareQuery { 8 9 private $ids; 10 private $phids; 11 private $authorPHIDs; 12 private $parentPHIDs; 13 14 private $needContent; 15 private $needRawContent; 16 private $needSnippets; 17 private $languages; 18 private $includeNoLanguage; 19 private $dateCreatedAfter; 20 private $dateCreatedBefore; 21 private $statuses; 22 23 24 public function withIDs(array $ids) { 25 $this->ids = $ids; 26 return $this; 27 } 28 29 public function withPHIDs(array $phids) { 30 $this->phids = $phids; 31 return $this; 32 } 33 34 public function withAuthorPHIDs(array $phids) { 35 $this->authorPHIDs = $phids; 36 return $this; 37 } 38 39 public function withParentPHIDs(array $phids) { 40 $this->parentPHIDs = $phids; 41 return $this; 42 } 43 44 public function needContent($need_content) { 45 $this->needContent = $need_content; 46 return $this; 47 } 48 49 public function needRawContent($need_raw_content) { 50 $this->needRawContent = $need_raw_content; 51 return $this; 52 } 53 54 public function needSnippets($need_snippets) { 55 $this->needSnippets = $need_snippets; 56 return $this; 57 } 58 59 public function withLanguages(array $languages) { 60 $this->includeNoLanguage = false; 61 foreach ($languages as $key => $language) { 62 if ($language === null) { 63 $languages[$key] = ''; 64 continue; 65 } 66 } 67 $this->languages = $languages; 68 return $this; 69 } 70 71 public function withDateCreatedBefore($date_created_before) { 72 $this->dateCreatedBefore = $date_created_before; 73 return $this; 74 } 75 76 public function withDateCreatedAfter($date_created_after) { 77 $this->dateCreatedAfter = $date_created_after; 78 return $this; 79 } 80 81 public function withStatuses(array $statuses) { 82 $this->statuses = $statuses; 83 return $this; 84 } 85 86 public function newResultObject() { 87 return new PhabricatorPaste(); 88 } 89 90 protected function didFilterPage(array $pastes) { 91 if ($this->needRawContent) { 92 $pastes = $this->loadRawContent($pastes); 93 } 94 95 if ($this->needContent) { 96 $pastes = $this->loadContent($pastes); 97 } 98 99 if ($this->needSnippets) { 100 $pastes = $this->loadSnippets($pastes); 101 } 102 103 return $pastes; 104 } 105 106 protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { 107 $where = parent::buildWhereClauseParts($conn); 108 109 if ($this->ids !== null) { 110 $where[] = qsprintf( 111 $conn, 112 'paste.id IN (%Ld)', 113 $this->ids); 114 } 115 116 if ($this->phids !== null) { 117 $where[] = qsprintf( 118 $conn, 119 'paste.phid IN (%Ls)', 120 $this->phids); 121 } 122 123 if ($this->authorPHIDs !== null) { 124 $where[] = qsprintf( 125 $conn, 126 'paste.authorPHID IN (%Ls)', 127 $this->authorPHIDs); 128 } 129 130 if ($this->parentPHIDs !== null) { 131 $where[] = qsprintf( 132 $conn, 133 'paste.parentPHID IN (%Ls)', 134 $this->parentPHIDs); 135 } 136 137 if ($this->languages !== null) { 138 $where[] = qsprintf( 139 $conn, 140 'paste.language IN (%Ls)', 141 $this->languages); 142 } 143 144 if ($this->dateCreatedAfter !== null) { 145 $where[] = qsprintf( 146 $conn, 147 'paste.dateCreated >= %d', 148 $this->dateCreatedAfter); 149 } 150 151 if ($this->dateCreatedBefore !== null) { 152 $where[] = qsprintf( 153 $conn, 154 'paste.dateCreated <= %d', 155 $this->dateCreatedBefore); 156 } 157 158 if ($this->statuses !== null) { 159 $where[] = qsprintf( 160 $conn, 161 'paste.status IN (%Ls)', 162 $this->statuses); 163 } 164 165 return $where; 166 } 167 168 protected function getPrimaryTableAlias() { 169 return 'paste'; 170 } 171 172 private function getContentCacheKey(PhabricatorPaste $paste) { 173 return implode( 174 ':', 175 array( 176 'P'.$paste->getID(), 177 $paste->getFilePHID(), 178 $paste->getLanguage(), 179 PhabricatorHash::digestForIndex($paste->getTitle()), 180 )); 181 } 182 183 private function getSnippetCacheKey(PhabricatorPaste $paste) { 184 return implode( 185 ':', 186 array( 187 'P'.$paste->getID(), 188 $paste->getFilePHID(), 189 $paste->getLanguage(), 190 'snippet', 191 'v2.1', 192 PhabricatorHash::digestForIndex($paste->getTitle()), 193 )); 194 } 195 196 private function loadRawContent(array $pastes) { 197 $file_phids = mpull($pastes, 'getFilePHID'); 198 $files = id(new PhabricatorFileQuery()) 199 ->setParentQuery($this) 200 ->setViewer($this->getViewer()) 201 ->withPHIDs($file_phids) 202 ->execute(); 203 $files = mpull($files, null, 'getPHID'); 204 205 foreach ($pastes as $key => $paste) { 206 $file = idx($files, $paste->getFilePHID()); 207 if (!$file) { 208 unset($pastes[$key]); 209 continue; 210 } 211 try { 212 $paste->attachRawContent($file->loadFileData()); 213 } catch (Exception $ex) { 214 // We can hit various sorts of file storage issues here. Just drop the 215 // paste if the file is dead. 216 unset($pastes[$key]); 217 continue; 218 } 219 } 220 221 return $pastes; 222 } 223 224 private function loadContent(array $pastes) { 225 $cache = new PhabricatorKeyValueDatabaseCache(); 226 227 $cache = new PhutilKeyValueCacheProfiler($cache); 228 $cache->setProfiler(PhutilServiceProfiler::getInstance()); 229 230 $keys = array(); 231 foreach ($pastes as $paste) { 232 $keys[] = $this->getContentCacheKey($paste); 233 } 234 235 $caches = $cache->getKeys($keys); 236 237 $need_raw = array(); 238 $have_cache = array(); 239 foreach ($pastes as $paste) { 240 $key = $this->getContentCacheKey($paste); 241 if (isset($caches[$key])) { 242 $paste->attachContent(phutil_safe_html($caches[$key])); 243 $have_cache[$paste->getPHID()] = true; 244 } else { 245 $need_raw[$key] = $paste; 246 } 247 } 248 249 if (!$need_raw) { 250 return $pastes; 251 } 252 253 $write_data = array(); 254 255 $have_raw = $this->loadRawContent($need_raw); 256 $have_raw = mpull($have_raw, null, 'getPHID'); 257 foreach ($pastes as $key => $paste) { 258 $paste_phid = $paste->getPHID(); 259 if (isset($have_cache[$paste_phid])) { 260 continue; 261 } 262 263 if (empty($have_raw[$paste_phid])) { 264 unset($pastes[$key]); 265 continue; 266 } 267 268 $content = $this->buildContent($paste); 269 $paste->attachContent($content); 270 $write_data[$this->getContentCacheKey($paste)] = (string)$content; 271 } 272 273 if ($write_data) { 274 $cache->setKeys($write_data); 275 } 276 277 return $pastes; 278 } 279 280 private function loadSnippets(array $pastes) { 281 $cache = new PhabricatorKeyValueDatabaseCache(); 282 283 $cache = new PhutilKeyValueCacheProfiler($cache); 284 $cache->setProfiler(PhutilServiceProfiler::getInstance()); 285 286 $keys = array(); 287 foreach ($pastes as $paste) { 288 $keys[] = $this->getSnippetCacheKey($paste); 289 } 290 291 $caches = $cache->getKeys($keys); 292 293 $need_raw = array(); 294 $have_cache = array(); 295 foreach ($pastes as $paste) { 296 $key = $this->getSnippetCacheKey($paste); 297 if (isset($caches[$key])) { 298 $snippet_data = phutil_json_decode($caches[$key]); 299 $snippet = new PhabricatorPasteSnippet( 300 phutil_safe_html($snippet_data['content']), 301 $snippet_data['type'], 302 $snippet_data['contentLineCount']); 303 $paste->attachSnippet($snippet); 304 $have_cache[$paste->getPHID()] = true; 305 } else { 306 $need_raw[$key] = $paste; 307 } 308 } 309 310 if (!$need_raw) { 311 return $pastes; 312 } 313 314 $write_data = array(); 315 316 $have_raw = $this->loadRawContent($need_raw); 317 $have_raw = mpull($have_raw, null, 'getPHID'); 318 foreach ($pastes as $key => $paste) { 319 $paste_phid = $paste->getPHID(); 320 if (isset($have_cache[$paste_phid])) { 321 continue; 322 } 323 324 if (empty($have_raw[$paste_phid])) { 325 unset($pastes[$key]); 326 continue; 327 } 328 329 $snippet = $this->buildSnippet($paste); 330 $paste->attachSnippet($snippet); 331 $snippet_data = array( 332 'content' => (string)$snippet->getContent(), 333 'type' => (string)$snippet->getType(), 334 'contentLineCount' => $snippet->getContentLineCount(), 335 ); 336 $write_data[$this->getSnippetCacheKey($paste)] = phutil_json_encode( 337 $snippet_data); 338 } 339 340 if ($write_data) { 341 $cache->setKeys($write_data); 342 } 343 344 return $pastes; 345 } 346 347 private function buildContent(PhabricatorPaste $paste) { 348 return $this->highlightSource( 349 $paste->getRawContent(), 350 $paste->getTitle(), 351 $paste->getLanguage()); 352 } 353 354 private function buildSnippet(PhabricatorPaste $paste) { 355 $snippet_type = PhabricatorPasteSnippet::FULL; 356 $snippet = $paste->getRawContent(); 357 358 $lines = phutil_split_lines($snippet); 359 $line_count = count($lines); 360 361 if (strlen($snippet) > 1024) { 362 $snippet_type = PhabricatorPasteSnippet::FIRST_BYTES; 363 $snippet = id(new PhutilUTF8StringTruncator()) 364 ->setMaximumBytes(1024) 365 ->setTerminator('') 366 ->truncateString($snippet); 367 } 368 369 if ($line_count > 5) { 370 $snippet_type = PhabricatorPasteSnippet::FIRST_LINES; 371 $snippet = implode('', array_slice($lines, 0, 5)); 372 } 373 374 return new PhabricatorPasteSnippet( 375 $this->highlightSource( 376 $snippet, 377 $paste->getTitle(), 378 $paste->getLanguage()), 379 $snippet_type, 380 $line_count); 381 } 382 383 private function highlightSource($source, $title, $language) { 384 if (empty($language)) { 385 return PhabricatorSyntaxHighlighter::highlightWithFilename( 386 $title, 387 $source); 388 } else { 389 return PhabricatorSyntaxHighlighter::highlightWithLanguage( 390 $language, 391 $source); 392 } 393 } 394 395 public function getQueryApplicationClass() { 396 return PhabricatorPasteApplication::class; 397 } 398 399}