@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<?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}