Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/diff/query/PhabricatorDiffInlineCommentQuery.php
12242 views
1
<?php
2
3
abstract class PhabricatorDiffInlineCommentQuery
4
extends PhabricatorApplicationTransactionCommentQuery {
5
6
const INLINE_CONTEXT_CACHE_VERSION = 1;
7
8
private $fixedStates;
9
private $needReplyToComments;
10
private $publishedComments;
11
private $publishableComments;
12
private $needHidden;
13
private $needAppliedDrafts;
14
private $needInlineContext;
15
16
abstract protected function buildInlineCommentWhereClauseParts(
17
AphrontDatabaseConnection $conn);
18
abstract public function withObjectPHIDs(array $phids);
19
abstract protected function loadHiddenCommentIDs(
20
$viewer_phid,
21
array $comments);
22
23
abstract protected function newInlineContextMap(array $inlines);
24
abstract protected function newInlineContextFromCacheData(array $map);
25
26
final public function withFixedStates(array $states) {
27
$this->fixedStates = $states;
28
return $this;
29
}
30
31
final public function needReplyToComments($need_reply_to) {
32
$this->needReplyToComments = $need_reply_to;
33
return $this;
34
}
35
36
final public function withPublishableComments($with_publishable) {
37
$this->publishableComments = $with_publishable;
38
return $this;
39
}
40
41
final public function withPublishedComments($with_published) {
42
$this->publishedComments = $with_published;
43
return $this;
44
}
45
46
final public function needHidden($need_hidden) {
47
$this->needHidden = $need_hidden;
48
return $this;
49
}
50
51
final public function needInlineContext($need_context) {
52
$this->needInlineContext = $need_context;
53
return $this;
54
}
55
56
final public function needAppliedDrafts($need_applied) {
57
$this->needAppliedDrafts = $need_applied;
58
return $this;
59
}
60
61
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
62
$where = parent::buildWhereClauseParts($conn);
63
$alias = $this->getPrimaryTableAlias();
64
65
foreach ($this->buildInlineCommentWhereClauseParts($conn) as $part) {
66
$where[] = $part;
67
}
68
69
if ($this->fixedStates !== null) {
70
$where[] = qsprintf(
71
$conn,
72
'%T.fixedState IN (%Ls)',
73
$alias,
74
$this->fixedStates);
75
}
76
77
$show_published = false;
78
$show_publishable = false;
79
80
if ($this->publishableComments !== null) {
81
if (!$this->publishableComments) {
82
throw new Exception(
83
pht(
84
'Querying for comments that are "not publishable" is '.
85
'not supported.'));
86
}
87
$show_publishable = true;
88
}
89
90
if ($this->publishedComments !== null) {
91
if (!$this->publishedComments) {
92
throw new Exception(
93
pht(
94
'Querying for comments that are "not published" is '.
95
'not supported.'));
96
}
97
$show_published = true;
98
}
99
100
if ($show_publishable || $show_published) {
101
$clauses = array();
102
103
if ($show_published) {
104
$clauses[] = qsprintf(
105
$conn,
106
'%T.transactionPHID IS NOT NULL',
107
$alias);
108
}
109
110
if ($show_publishable) {
111
$viewer = $this->getViewer();
112
$viewer_phid = $viewer->getPHID();
113
114
// If the viewer has a PHID, unpublished comments they authored and
115
// have not deleted are visible.
116
if ($viewer_phid) {
117
$clauses[] = qsprintf(
118
$conn,
119
'%T.authorPHID = %s
120
AND %T.isDeleted = 0
121
AND %T.transactionPHID IS NULL ',
122
$alias,
123
$viewer_phid,
124
$alias,
125
$alias);
126
}
127
}
128
129
// We can end up with a known-empty query if we (for example) query for
130
// publishable comments and the viewer is logged-out.
131
if (!$clauses) {
132
throw new PhabricatorEmptyQueryException();
133
}
134
135
$where[] = qsprintf(
136
$conn,
137
'%LO',
138
$clauses);
139
}
140
141
return $where;
142
}
143
144
protected function willFilterPage(array $inlines) {
145
$viewer = $this->getViewer();
146
147
if ($this->needReplyToComments) {
148
$reply_phids = array();
149
foreach ($inlines as $inline) {
150
$reply_phid = $inline->getReplyToCommentPHID();
151
if ($reply_phid) {
152
$reply_phids[] = $reply_phid;
153
}
154
}
155
156
if ($reply_phids) {
157
$reply_inlines = newv(get_class($this), array())
158
->setViewer($this->getViewer())
159
->setParentQuery($this)
160
->withPHIDs($reply_phids)
161
->execute();
162
$reply_inlines = mpull($reply_inlines, null, 'getPHID');
163
} else {
164
$reply_inlines = array();
165
}
166
167
foreach ($inlines as $key => $inline) {
168
$reply_phid = $inline->getReplyToCommentPHID();
169
if (!$reply_phid) {
170
$inline->attachReplyToComment(null);
171
continue;
172
}
173
$reply = idx($reply_inlines, $reply_phid);
174
if (!$reply) {
175
$this->didRejectResult($inline);
176
unset($inlines[$key]);
177
continue;
178
}
179
$inline->attachReplyToComment($reply);
180
}
181
}
182
183
if (!$inlines) {
184
return $inlines;
185
}
186
187
$need_drafts = $this->needAppliedDrafts;
188
$drop_void = $this->publishableComments;
189
$convert_objects = ($need_drafts || $drop_void);
190
191
if ($convert_objects) {
192
$inlines = mpull($inlines, 'newInlineCommentObject');
193
194
PhabricatorInlineComment::loadAndAttachVersionedDrafts(
195
$viewer,
196
$inlines);
197
198
if ($need_drafts) {
199
// Don't count void inlines when considering draft state.
200
foreach ($inlines as $key => $inline) {
201
if ($inline->isVoidComment($viewer)) {
202
$this->didRejectResult($inline->getStorageObject());
203
unset($inlines[$key]);
204
continue;
205
}
206
207
// For other inlines: if they have a nonempty draft state, set their
208
// content to the draft state content. We want to submit the comment
209
// as it is currently shown to the user, not as it was stored the last
210
// time they clicked "Save".
211
212
$draft_state = $inline->getContentStateForEdit($viewer);
213
if (!$draft_state->isEmptyContentState()) {
214
$inline->setContentState($draft_state);
215
}
216
}
217
}
218
219
// If we're loading publishable comments, discard any comments that are
220
// empty.
221
if ($drop_void) {
222
foreach ($inlines as $key => $inline) {
223
if ($inline->getTransactionPHID()) {
224
continue;
225
}
226
227
if ($inline->isVoidComment($viewer)) {
228
$this->didRejectResult($inline->getStorageObject());
229
unset($inlines[$key]);
230
continue;
231
}
232
}
233
}
234
235
$inlines = mpull($inlines, 'getStorageObject');
236
}
237
238
return $inlines;
239
}
240
241
protected function didFilterPage(array $inlines) {
242
$viewer = $this->getViewer();
243
244
if ($this->needHidden) {
245
$viewer_phid = $viewer->getPHID();
246
247
if ($viewer_phid) {
248
$hidden = $this->loadHiddenCommentIDs(
249
$viewer_phid,
250
$inlines);
251
} else {
252
$hidden = array();
253
}
254
255
foreach ($inlines as $inline) {
256
$inline->attachIsHidden(isset($hidden[$inline->getID()]));
257
}
258
}
259
260
if ($this->needInlineContext) {
261
$need_context = array();
262
foreach ($inlines as $inline) {
263
$object = $inline->newInlineCommentObject();
264
265
if ($object->getDocumentEngineKey() !== null) {
266
$inline->attachInlineContext(null);
267
continue;
268
}
269
270
$need_context[] = $inline;
271
}
272
273
if ($need_context) {
274
$this->loadInlineCommentContext($need_context);
275
}
276
}
277
278
return $inlines;
279
}
280
281
private function loadInlineCommentContext(array $inlines) {
282
$cache_keys = array();
283
foreach ($inlines as $key => $inline) {
284
$object = $inline->newInlineCommentObject();
285
$fragment = $object->getInlineCommentCacheFragment();
286
287
if ($fragment === null) {
288
continue;
289
}
290
291
$cache_keys[$key] = sprintf(
292
'%s.context(v%d)',
293
$fragment,
294
self::INLINE_CONTEXT_CACHE_VERSION);
295
}
296
297
$cache = PhabricatorCaches::getMutableStructureCache();
298
299
$cache_map = $cache->getKeys($cache_keys);
300
301
$context_map = array();
302
$need_construct = array();
303
304
foreach ($inlines as $key => $inline) {
305
$cache_key = idx($cache_keys, $key);
306
307
if ($cache_key !== null) {
308
if (array_key_exists($cache_key, $cache_map)) {
309
$cache_data = $cache_map[$cache_key];
310
$context_map[$key] = $this->newInlineContextFromCacheData(
311
$cache_data);
312
continue;
313
}
314
}
315
316
$need_construct[$key] = $inline;
317
}
318
319
if ($need_construct) {
320
$construct_map = $this->newInlineContextMap($need_construct);
321
322
$write_map = array();
323
foreach ($construct_map as $key => $context) {
324
if ($context === null) {
325
$cache_data = $context;
326
} else {
327
$cache_data = $this->newCacheDataFromInlineContext($context);
328
}
329
330
$cache_key = idx($cache_keys, $key);
331
if ($cache_key !== null) {
332
$write_map[$cache_key] = $cache_data;
333
}
334
}
335
336
if ($write_map) {
337
$cache->setKeys($write_map);
338
}
339
340
$context_map += $construct_map;
341
}
342
343
foreach ($inlines as $key => $inline) {
344
$inline->attachInlineContext(idx($context_map, $key));
345
}
346
}
347
348
protected function newCacheDataFromInlineContext(
349
PhabricatorInlineCommentContext $context) {
350
return $context->newCacheDataMap();
351
}
352
353
final protected function simplifyContext(array $lines, $is_head) {
354
// We want to provide the smallest amount of context we can while still
355
// being useful, since the actual code is visible nearby and showing a
356
// ton of context is silly.
357
358
// Examine each line until we find one that looks "useful" (not just
359
// whitespace or a single bracket). Once we find a useful piece of context
360
// to anchor the text, discard the rest of the lines beyond it.
361
362
if ($is_head) {
363
$lines = array_reverse($lines, true);
364
}
365
366
$saw_context = false;
367
foreach ($lines as $key => $line) {
368
if ($saw_context) {
369
unset($lines[$key]);
370
continue;
371
}
372
373
$saw_context = (strlen(trim($line)) > 3);
374
}
375
376
if ($is_head) {
377
$lines = array_reverse($lines, true);
378
}
379
380
return $lines;
381
}
382
}
383
384