Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/differential/mail/DifferentialInlineCommentMailView.php
12262 views
1
<?php
2
3
final class DifferentialInlineCommentMailView
4
extends DifferentialMailView {
5
6
private $viewer;
7
private $inlines;
8
private $changesets;
9
private $authors;
10
11
public function setViewer(PhabricatorUser $viewer) {
12
$this->viewer = $viewer;
13
return $this;
14
}
15
16
public function getViewer() {
17
return $this->viewer;
18
}
19
20
public function setInlines($inlines) {
21
$this->inlines = $inlines;
22
return $this;
23
}
24
25
public function getInlines() {
26
return $this->inlines;
27
}
28
29
public function buildMailSection() {
30
$inlines = $this->getInlines();
31
32
$comments = mpull($inlines, 'getComment');
33
$comments = mpull($comments, null, 'getPHID');
34
$parents = $this->loadParents($comments);
35
$all_comments = $comments + $parents;
36
37
$this->changesets = $this->loadChangesets($all_comments);
38
$this->authors = $this->loadAuthors($all_comments);
39
$groups = $this->groupInlines($inlines);
40
41
$hunk_parser = new DifferentialHunkParser();
42
43
$spacer_text = null;
44
$spacer_html = phutil_tag('br');
45
46
$section = new PhabricatorMetaMTAMailSection();
47
48
$last_group_key = last_key($groups);
49
foreach ($groups as $changeset_id => $group) {
50
$changeset = $this->getChangeset($changeset_id);
51
if (!$changeset) {
52
continue;
53
}
54
55
$is_last_group = ($changeset_id == $last_group_key);
56
57
$last_inline_key = last_key($group);
58
foreach ($group as $inline_key => $inline) {
59
$comment = $inline->getComment();
60
$parent_phid = $comment->getReplyToCommentPHID();
61
62
$inline_object = $comment->newInlineCommentObject();
63
$document_engine_key = $inline_object->getDocumentEngineKey();
64
65
$is_last_inline = ($inline_key == $last_inline_key);
66
67
$context_text = null;
68
$context_html = null;
69
70
if ($parent_phid) {
71
$parent = idx($parents, $parent_phid);
72
if ($parent) {
73
$context_text = $this->renderInline($parent, false, true);
74
$context_html = $this->renderInline($parent, true, true);
75
}
76
} else if ($document_engine_key !== null) {
77
// See T13513. If an inline was left on a rendered document, don't
78
// include the patch context. Document engines currently can not
79
// render to mail targets, and using the line numbers as raw source
80
// lines produces misleading context.
81
82
$patch_text = null;
83
$context_text = $this->renderPatch($comment, $patch_text, false);
84
85
$patch_html = null;
86
$context_html = $this->renderPatch($comment, $patch_html, true);
87
} else {
88
$patch_text = $this->getPatch($hunk_parser, $comment, false);
89
$context_text = $this->renderPatch($comment, $patch_text, false);
90
91
$patch_html = $this->getPatch($hunk_parser, $comment, true);
92
$context_html = $this->renderPatch($comment, $patch_html, true);
93
}
94
95
$render_text = $this->renderInline($comment, false, false);
96
$render_html = $this->renderInline($comment, true, false);
97
98
$section->addPlaintextFragment($context_text);
99
$section->addPlaintextFragment($spacer_text);
100
$section->addPlaintextFragment($render_text);
101
102
$html_fragment = $this->renderContentBox(
103
array(
104
$context_html,
105
$render_html,
106
));
107
108
$section->addHTMLFragment($html_fragment);
109
110
if (!$is_last_group || !$is_last_inline) {
111
$section->addPlaintextFragment($spacer_text);
112
$section->addHTMLFragment($spacer_html);
113
}
114
}
115
}
116
117
return $section;
118
}
119
120
private function loadChangesets(array $comments) {
121
if (!$comments) {
122
return array();
123
}
124
125
$ids = array();
126
foreach ($comments as $comment) {
127
$ids[] = $comment->getChangesetID();
128
}
129
130
$changesets = id(new DifferentialChangesetQuery())
131
->setViewer($this->getViewer())
132
->withIDs($ids)
133
->needHunks(true)
134
->execute();
135
136
return mpull($changesets, null, 'getID');
137
}
138
139
private function loadParents(array $comments) {
140
$viewer = $this->getViewer();
141
142
$phids = array();
143
foreach ($comments as $comment) {
144
$parent_phid = $comment->getReplyToCommentPHID();
145
if (!$parent_phid) {
146
continue;
147
}
148
$phids[] = $parent_phid;
149
}
150
151
if (!$phids) {
152
return array();
153
}
154
155
$parents = id(new DifferentialDiffInlineCommentQuery())
156
->setViewer($viewer)
157
->withPHIDs($phids)
158
->execute();
159
160
return mpull($parents, null, 'getPHID');
161
}
162
163
private function loadAuthors(array $comments) {
164
$viewer = $this->getViewer();
165
166
$phids = array();
167
foreach ($comments as $comment) {
168
$author_phid = $comment->getAuthorPHID();
169
if (!$author_phid) {
170
continue;
171
}
172
$phids[] = $author_phid;
173
}
174
175
if (!$phids) {
176
return array();
177
}
178
179
return $viewer->loadHandles($phids);
180
}
181
182
private function groupInlines(array $inlines) {
183
return DifferentialTransactionComment::sortAndGroupInlines(
184
$inlines,
185
$this->changesets);
186
}
187
188
private function renderInline(
189
DifferentialTransactionComment $comment,
190
$is_html,
191
$is_quote) {
192
193
$changeset = $this->getChangeset($comment->getChangesetID());
194
if (!$changeset) {
195
return null;
196
}
197
198
$content = $comment->getContent();
199
$content = $this->renderRemarkupContent($content, $is_html);
200
201
if ($is_quote) {
202
$header = $this->renderHeader($comment, $is_html, true);
203
} else {
204
$header = null;
205
}
206
207
if ($is_html) {
208
$style = array(
209
'margin: 8px 0;',
210
'padding: 0 12px;',
211
);
212
213
if ($is_quote) {
214
$style[] = 'color: #74777D;';
215
}
216
217
$content = phutil_tag(
218
'div',
219
array(
220
'style' => implode(' ', $style),
221
),
222
$content);
223
}
224
225
$parts = array(
226
$header,
227
"\n",
228
$content,
229
);
230
231
if (!$is_html) {
232
$parts = implode('', $parts);
233
$parts = trim($parts);
234
}
235
236
if ($is_quote) {
237
if ($is_html) {
238
$parts = $this->quoteHTML($parts);
239
} else {
240
$parts = $this->quoteText($parts);
241
}
242
}
243
244
return $parts;
245
}
246
247
private function renderRemarkupContent($content, $is_html) {
248
$viewer = $this->getViewer();
249
$production_uri = PhabricatorEnv::getProductionURI('/');
250
251
if ($is_html) {
252
$mode = PhutilRemarkupEngine::MODE_HTML_MAIL;
253
} else {
254
$mode = PhutilRemarkupEngine::MODE_TEXT;
255
}
256
257
$attributes = array(
258
'style' => 'padding: 0; margin: 8px;',
259
);
260
261
$engine = PhabricatorMarkupEngine::newMarkupEngine(array())
262
->setConfig('viewer', $viewer)
263
->setConfig('uri.base', $production_uri)
264
->setConfig('default.p.attributes', $attributes)
265
->setMode($mode);
266
267
try {
268
return $engine->markupText($content);
269
} catch (Exception $ex) {
270
return $content;
271
}
272
}
273
274
private function getChangeset($id) {
275
return idx($this->changesets, $id);
276
}
277
278
private function getAuthor($phid) {
279
if (isset($this->authors[$phid])) {
280
return $this->authors[$phid];
281
}
282
return null;
283
}
284
285
private function quoteText($block) {
286
$block = phutil_split_lines($block);
287
foreach ($block as $key => $line) {
288
$block[$key] = '> '.$line;
289
}
290
291
return implode('', $block);
292
}
293
294
private function quoteHTML($block) {
295
$styles = array(
296
'padding: 0;',
297
'background: #F7F7F7;',
298
'border-color: #e3e4e8;',
299
'border-style: solid;',
300
'border-width: 0 0 1px 0;',
301
'margin: 0;',
302
);
303
304
$styles = implode(' ', $styles);
305
306
return phutil_tag(
307
'div',
308
array(
309
'style' => $styles,
310
),
311
$block);
312
}
313
314
private function getPatch(
315
DifferentialHunkParser $parser,
316
DifferentialTransactionComment $comment,
317
$is_html) {
318
319
$changeset = $this->getChangeset($comment->getChangesetID());
320
$is_new = $comment->getIsNewFile();
321
$start = $comment->getLineNumber();
322
$length = $comment->getLineLength();
323
324
// By default, show one line of context around the target inline.
325
$context = 1;
326
327
// If the inline is at least 3 lines long, don't show any extra context.
328
if ($length >= 2) {
329
$context = 0;
330
}
331
332
// If the inline is more than 7 lines long, only show the first 7 lines.
333
if ($length >= 6) {
334
$length = 6;
335
}
336
337
if (!$is_html) {
338
$hunks = $changeset->getHunks();
339
$patch = $parser->makeContextDiff(
340
$hunks,
341
$is_new,
342
$start,
343
$length,
344
$context);
345
$patch = phutil_split_lines($patch);
346
347
// Remove the "@@ -x,y +u,v @@" line.
348
array_shift($patch);
349
350
return implode('', $patch);
351
}
352
353
$viewer = $this->getViewer();
354
$engine = new PhabricatorMarkupEngine();
355
356
if ($is_new) {
357
$offset_mode = 'new';
358
} else {
359
$offset_mode = 'old';
360
}
361
362
// See PHI894. Use the parse cache since we can end up with a large
363
// rendering cost otherwise when users or bots leave hundreds of inline
364
// comments on diffs with long recipient lists.
365
$cache_key = $changeset->getID();
366
367
$viewstate = new PhabricatorChangesetViewState();
368
369
$parser = id(new DifferentialChangesetParser())
370
->setRenderCacheKey($cache_key)
371
->setViewer($viewer)
372
->setViewstate($viewstate)
373
->setChangeset($changeset)
374
->setOffsetMode($offset_mode)
375
->setMarkupEngine($engine);
376
377
$parser->setRenderer(new DifferentialChangesetOneUpMailRenderer());
378
379
return $parser->render(
380
$start - $context,
381
$length + (2 * $context),
382
array());
383
}
384
385
private function renderPatch(
386
DifferentialTransactionComment $comment,
387
$patch,
388
$is_html) {
389
390
if ($is_html) {
391
if ($patch !== null) {
392
$patch = $this->renderCodeBlock($patch);
393
}
394
}
395
396
$header = $this->renderHeader($comment, $is_html, false);
397
398
if ($patch === null) {
399
$patch = array(
400
$header,
401
);
402
} else {
403
$patch = array(
404
$header,
405
"\n",
406
$patch,
407
);
408
}
409
410
if (!$is_html) {
411
$patch = implode('', $patch);
412
$patch = $this->quoteText($patch);
413
} else {
414
$patch = $this->quoteHTML($patch);
415
}
416
417
return $patch;
418
}
419
420
private function renderHeader(
421
DifferentialTransactionComment $comment,
422
$is_html,
423
$with_author) {
424
425
$changeset = $this->getChangeset($comment->getChangesetID());
426
$path = $changeset->getFilename();
427
428
// Only show the filename.
429
$path = basename($path);
430
431
$start = $comment->getLineNumber();
432
$length = $comment->getLineLength();
433
if ($length) {
434
$range = pht('%s-%s', $start, $start + $length);
435
} else {
436
$range = $start;
437
}
438
439
$header = "{$path}:{$range}";
440
if ($is_html) {
441
$header = $this->renderHeaderBold($header);
442
}
443
444
if ($with_author) {
445
$author = $this->getAuthor($comment->getAuthorPHID());
446
} else {
447
$author = null;
448
}
449
450
if ($author) {
451
$byline = $author->getName();
452
453
if ($is_html) {
454
$byline = $this->renderHeaderBold($byline);
455
}
456
457
$header = pht('%s wrote in %s', $byline, $header);
458
}
459
460
if ($is_html) {
461
$link_href = $this->getInlineURI($comment);
462
if ($link_href) {
463
$link_style = array(
464
'float: right;',
465
'text-decoration: none;',
466
);
467
468
$link = phutil_tag(
469
'a',
470
array(
471
'style' => implode(' ', $link_style),
472
'href' => $link_href,
473
),
474
array(
475
pht('View Inline'),
476
477
// See PHI920. Add a space after the link so we render this into
478
// the document:
479
//
480
// View Inline filename.txt
481
//
482
// Otherwise, we render "Inlinefilename.txt" and double-clicking
483
// the file name selects the word "Inline" as well.
484
' ',
485
));
486
} else {
487
$link = null;
488
}
489
490
$header = $this->renderHeaderBlock(array($link, $header));
491
}
492
493
return $header;
494
}
495
496
private function getInlineURI(DifferentialTransactionComment $comment) {
497
$changeset = $this->getChangeset($comment->getChangesetID());
498
if (!$changeset) {
499
return null;
500
}
501
502
$diff = $changeset->getDiff();
503
if (!$diff) {
504
return null;
505
}
506
507
$revision = $diff->getRevision();
508
if (!$revision) {
509
return null;
510
}
511
512
$link_href = '/'.$revision->getMonogram().'#inline-'.$comment->getID();
513
$link_href = PhabricatorEnv::getProductionURI($link_href);
514
515
return $link_href;
516
}
517
518
519
}
520
521