Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
12262 views
1
<?php
2
3
abstract class DifferentialChangesetHTMLRenderer
4
extends DifferentialChangesetRenderer {
5
6
public static function getHTMLRendererByKey($key) {
7
switch ($key) {
8
case '1up':
9
return new DifferentialChangesetOneUpRenderer();
10
case '2up':
11
default:
12
return new DifferentialChangesetTwoUpRenderer();
13
}
14
throw new Exception(pht('Unknown HTML renderer "%s"!', $key));
15
}
16
17
abstract protected function getRendererTableClass();
18
abstract public function getRowScaffoldForInline(
19
PHUIDiffInlineCommentView $view);
20
21
protected function renderChangeTypeHeader($force) {
22
$changeset = $this->getChangeset();
23
24
$change = $changeset->getChangeType();
25
$file = $changeset->getFileType();
26
27
$messages = array();
28
switch ($change) {
29
30
case DifferentialChangeType::TYPE_ADD:
31
switch ($file) {
32
case DifferentialChangeType::FILE_TEXT:
33
$messages[] = pht('This file was added.');
34
break;
35
case DifferentialChangeType::FILE_IMAGE:
36
$messages[] = pht('This image was added.');
37
break;
38
case DifferentialChangeType::FILE_DIRECTORY:
39
$messages[] = pht('This directory was added.');
40
break;
41
case DifferentialChangeType::FILE_BINARY:
42
$messages[] = pht('This binary file was added.');
43
break;
44
case DifferentialChangeType::FILE_SYMLINK:
45
$messages[] = pht('This symlink was added.');
46
break;
47
case DifferentialChangeType::FILE_SUBMODULE:
48
$messages[] = pht('This submodule was added.');
49
break;
50
}
51
break;
52
53
case DifferentialChangeType::TYPE_DELETE:
54
switch ($file) {
55
case DifferentialChangeType::FILE_TEXT:
56
$messages[] = pht('This file was deleted.');
57
break;
58
case DifferentialChangeType::FILE_IMAGE:
59
$messages[] = pht('This image was deleted.');
60
break;
61
case DifferentialChangeType::FILE_DIRECTORY:
62
$messages[] = pht('This directory was deleted.');
63
break;
64
case DifferentialChangeType::FILE_BINARY:
65
$messages[] = pht('This binary file was deleted.');
66
break;
67
case DifferentialChangeType::FILE_SYMLINK:
68
$messages[] = pht('This symlink was deleted.');
69
break;
70
case DifferentialChangeType::FILE_SUBMODULE:
71
$messages[] = pht('This submodule was deleted.');
72
break;
73
}
74
break;
75
76
case DifferentialChangeType::TYPE_MOVE_HERE:
77
$from = phutil_tag('strong', array(), $changeset->getOldFile());
78
switch ($file) {
79
case DifferentialChangeType::FILE_TEXT:
80
$messages[] = pht('This file was moved from %s.', $from);
81
break;
82
case DifferentialChangeType::FILE_IMAGE:
83
$messages[] = pht('This image was moved from %s.', $from);
84
break;
85
case DifferentialChangeType::FILE_DIRECTORY:
86
$messages[] = pht('This directory was moved from %s.', $from);
87
break;
88
case DifferentialChangeType::FILE_BINARY:
89
$messages[] = pht('This binary file was moved from %s.', $from);
90
break;
91
case DifferentialChangeType::FILE_SYMLINK:
92
$messages[] = pht('This symlink was moved from %s.', $from);
93
break;
94
case DifferentialChangeType::FILE_SUBMODULE:
95
$messages[] = pht('This submodule was moved from %s.', $from);
96
break;
97
}
98
break;
99
100
case DifferentialChangeType::TYPE_COPY_HERE:
101
$from = phutil_tag('strong', array(), $changeset->getOldFile());
102
switch ($file) {
103
case DifferentialChangeType::FILE_TEXT:
104
$messages[] = pht('This file was copied from %s.', $from);
105
break;
106
case DifferentialChangeType::FILE_IMAGE:
107
$messages[] = pht('This image was copied from %s.', $from);
108
break;
109
case DifferentialChangeType::FILE_DIRECTORY:
110
$messages[] = pht('This directory was copied from %s.', $from);
111
break;
112
case DifferentialChangeType::FILE_BINARY:
113
$messages[] = pht('This binary file was copied from %s.', $from);
114
break;
115
case DifferentialChangeType::FILE_SYMLINK:
116
$messages[] = pht('This symlink was copied from %s.', $from);
117
break;
118
case DifferentialChangeType::FILE_SUBMODULE:
119
$messages[] = pht('This submodule was copied from %s.', $from);
120
break;
121
}
122
break;
123
124
case DifferentialChangeType::TYPE_MOVE_AWAY:
125
$paths = phutil_tag(
126
'strong',
127
array(),
128
implode(', ', $changeset->getAwayPaths()));
129
switch ($file) {
130
case DifferentialChangeType::FILE_TEXT:
131
$messages[] = pht('This file was moved to %s.', $paths);
132
break;
133
case DifferentialChangeType::FILE_IMAGE:
134
$messages[] = pht('This image was moved to %s.', $paths);
135
break;
136
case DifferentialChangeType::FILE_DIRECTORY:
137
$messages[] = pht('This directory was moved to %s.', $paths);
138
break;
139
case DifferentialChangeType::FILE_BINARY:
140
$messages[] = pht('This binary file was moved to %s.', $paths);
141
break;
142
case DifferentialChangeType::FILE_SYMLINK:
143
$messages[] = pht('This symlink was moved to %s.', $paths);
144
break;
145
case DifferentialChangeType::FILE_SUBMODULE:
146
$messages[] = pht('This submodule was moved to %s.', $paths);
147
break;
148
}
149
break;
150
151
case DifferentialChangeType::TYPE_COPY_AWAY:
152
$paths = phutil_tag(
153
'strong',
154
array(),
155
implode(', ', $changeset->getAwayPaths()));
156
switch ($file) {
157
case DifferentialChangeType::FILE_TEXT:
158
$messages[] = pht('This file was copied to %s.', $paths);
159
break;
160
case DifferentialChangeType::FILE_IMAGE:
161
$messages[] = pht('This image was copied to %s.', $paths);
162
break;
163
case DifferentialChangeType::FILE_DIRECTORY:
164
$messages[] = pht('This directory was copied to %s.', $paths);
165
break;
166
case DifferentialChangeType::FILE_BINARY:
167
$messages[] = pht('This binary file was copied to %s.', $paths);
168
break;
169
case DifferentialChangeType::FILE_SYMLINK:
170
$messages[] = pht('This symlink was copied to %s.', $paths);
171
break;
172
case DifferentialChangeType::FILE_SUBMODULE:
173
$messages[] = pht('This submodule was copied to %s.', $paths);
174
break;
175
}
176
break;
177
178
case DifferentialChangeType::TYPE_MULTICOPY:
179
$paths = phutil_tag(
180
'strong',
181
array(),
182
implode(', ', $changeset->getAwayPaths()));
183
switch ($file) {
184
case DifferentialChangeType::FILE_TEXT:
185
$messages[] = pht(
186
'This file was deleted after being copied to %s.',
187
$paths);
188
break;
189
case DifferentialChangeType::FILE_IMAGE:
190
$messages[] = pht(
191
'This image was deleted after being copied to %s.',
192
$paths);
193
break;
194
case DifferentialChangeType::FILE_DIRECTORY:
195
$messages[] = pht(
196
'This directory was deleted after being copied to %s.',
197
$paths);
198
break;
199
case DifferentialChangeType::FILE_BINARY:
200
$messages[] = pht(
201
'This binary file was deleted after being copied to %s.',
202
$paths);
203
break;
204
case DifferentialChangeType::FILE_SYMLINK:
205
$messages[] = pht(
206
'This symlink was deleted after being copied to %s.',
207
$paths);
208
break;
209
case DifferentialChangeType::FILE_SUBMODULE:
210
$messages[] = pht(
211
'This submodule was deleted after being copied to %s.',
212
$paths);
213
break;
214
}
215
break;
216
217
default:
218
switch ($file) {
219
case DifferentialChangeType::FILE_TEXT:
220
// This is the default case, so we only render this header if
221
// forced to since it's not very useful.
222
if ($force) {
223
$messages[] = pht('This file was not modified.');
224
}
225
break;
226
case DifferentialChangeType::FILE_IMAGE:
227
$messages[] = pht('This is an image.');
228
break;
229
case DifferentialChangeType::FILE_DIRECTORY:
230
$messages[] = pht('This is a directory.');
231
break;
232
case DifferentialChangeType::FILE_BINARY:
233
$messages[] = pht('This is a binary file.');
234
break;
235
case DifferentialChangeType::FILE_SYMLINK:
236
$messages[] = pht('This is a symlink.');
237
break;
238
case DifferentialChangeType::FILE_SUBMODULE:
239
$messages[] = pht('This is a submodule.');
240
break;
241
}
242
break;
243
}
244
245
return $this->formatHeaderMessages($messages);
246
}
247
248
protected function renderUndershieldHeader() {
249
$messages = array();
250
251
$changeset = $this->getChangeset();
252
253
$file = $changeset->getFileType();
254
255
// If this is a text file with at least one hunk, we may have converted
256
// the text encoding. In this case, show a note.
257
$show_encoding = ($file == DifferentialChangeType::FILE_TEXT) &&
258
($changeset->getHunks());
259
260
if ($show_encoding) {
261
$encoding = $this->getOriginalCharacterEncoding();
262
if ($encoding != 'utf8') {
263
if ($encoding) {
264
$messages[] = pht(
265
'This file was converted from %s for display.',
266
phutil_tag('strong', array(), $encoding));
267
} else {
268
$messages[] = pht('This file uses an unknown character encoding.');
269
}
270
}
271
}
272
273
$blocks = $this->getDocumentEngineBlocks();
274
if ($blocks) {
275
foreach ($blocks->getMessages() as $message) {
276
$messages[] = $message;
277
}
278
} else {
279
if ($this->getHighlightingDisabled()) {
280
$byte_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT;
281
$byte_limit = phutil_format_bytes($byte_limit);
282
$messages[] = pht(
283
'This file is larger than %s, so syntax highlighting is '.
284
'disabled by default.',
285
$byte_limit);
286
}
287
}
288
289
return $this->formatHeaderMessages($messages);
290
}
291
292
private function formatHeaderMessages(array $messages) {
293
if (!$messages) {
294
return null;
295
}
296
297
foreach ($messages as $key => $message) {
298
$messages[$key] = phutil_tag('li', array(), $message);
299
}
300
301
return phutil_tag(
302
'ul',
303
array(
304
'class' => 'differential-meta-notice',
305
),
306
$messages);
307
}
308
309
protected function renderPropertyChangeHeader() {
310
$changeset = $this->getChangeset();
311
list($old, $new) = $this->getChangesetProperties($changeset);
312
313
// If we don't have any property changes, don't render this table.
314
if ($old === $new) {
315
return null;
316
}
317
318
$keys = array_keys($old + $new);
319
sort($keys);
320
321
$key_map = array(
322
'unix:filemode' => pht('File Mode'),
323
'file:dimensions' => pht('Image Dimensions'),
324
'file:mimetype' => pht('MIME Type'),
325
'file:size' => pht('File Size'),
326
);
327
328
$rows = array();
329
foreach ($keys as $key) {
330
$oval = idx($old, $key);
331
$nval = idx($new, $key);
332
if ($oval !== $nval) {
333
if ($oval === null) {
334
$oval = phutil_tag('em', array(), 'null');
335
} else {
336
$oval = phutil_escape_html_newlines($oval);
337
}
338
339
if ($nval === null) {
340
$nval = phutil_tag('em', array(), 'null');
341
} else {
342
$nval = phutil_escape_html_newlines($nval);
343
}
344
345
$readable_key = idx($key_map, $key, $key);
346
347
$row = array(
348
$readable_key,
349
$oval,
350
$nval,
351
);
352
$rows[] = $row;
353
354
}
355
}
356
357
$classes = array('', 'oval', 'nval');
358
$headers = array(
359
pht('Property'),
360
pht('Old Value'),
361
pht('New Value'),
362
);
363
$table = id(new AphrontTableView($rows))
364
->setHeaders($headers)
365
->setColumnClasses($classes);
366
return phutil_tag(
367
'div',
368
array(
369
'class' => 'differential-property-table',
370
),
371
$table);
372
}
373
374
public function renderShield($message, $force = 'default') {
375
$end = count($this->getOldLines());
376
$reference = $this->getRenderingReference();
377
378
if ($force !== 'text' &&
379
$force !== 'none' &&
380
$force !== 'default') {
381
throw new Exception(
382
pht(
383
"Invalid '%s' parameter '%s'!",
384
'force',
385
$force));
386
}
387
388
$range = "0-{$end}";
389
if ($force == 'text') {
390
// If we're forcing text, force the whole file to be rendered.
391
$range = "{$range}/0-{$end}";
392
}
393
394
$meta = array(
395
'ref' => $reference,
396
'range' => $range,
397
);
398
399
$content = array();
400
$content[] = $message;
401
if ($force !== 'none') {
402
$content[] = ' ';
403
$content[] = javelin_tag(
404
'a',
405
array(
406
'mustcapture' => true,
407
'sigil' => 'show-more',
408
'class' => 'complete',
409
'href' => '#',
410
'meta' => $meta,
411
),
412
pht('Show File Contents'));
413
}
414
415
return $this->wrapChangeInTable(
416
javelin_tag(
417
'tr',
418
array(
419
'sigil' => 'context-target',
420
),
421
phutil_tag(
422
'td',
423
array(
424
'class' => 'differential-shield',
425
'colspan' => 6,
426
),
427
$content)));
428
}
429
430
abstract protected function renderColgroup();
431
432
433
protected function wrapChangeInTable($content) {
434
if (!$content) {
435
return null;
436
}
437
438
$classes = array();
439
$classes[] = 'differential-diff';
440
$classes[] = 'remarkup-code';
441
$classes[] = 'PhabricatorMonospaced';
442
$classes[] = $this->getRendererTableClass();
443
444
$sigils = array();
445
$sigils[] = 'differential-diff';
446
foreach ($this->getTableSigils() as $sigil) {
447
$sigils[] = $sigil;
448
}
449
450
return javelin_tag(
451
'table',
452
array(
453
'class' => implode(' ', $classes),
454
'sigil' => implode(' ', $sigils),
455
),
456
array(
457
$this->renderColgroup(),
458
$content,
459
));
460
}
461
462
protected function getTableSigils() {
463
return array();
464
}
465
466
protected function buildInlineComment(
467
PhabricatorInlineComment $comment,
468
$on_right = false) {
469
470
$viewer = $this->getUser();
471
$edit = $viewer &&
472
($comment->getAuthorPHID() == $viewer->getPHID()) &&
473
($comment->isDraft())
474
&& $this->getShowEditAndReplyLinks();
475
$allow_reply = (bool)$viewer && $this->getShowEditAndReplyLinks();
476
$allow_done = !$comment->isDraft() && $this->getCanMarkDone();
477
478
return id(new PHUIDiffInlineCommentDetailView())
479
->setViewer($viewer)
480
->setInlineComment($comment)
481
->setIsOnRight($on_right)
482
->setHandles($this->getHandles())
483
->setMarkupEngine($this->getMarkupEngine())
484
->setEditable($edit)
485
->setAllowReply($allow_reply)
486
->setCanMarkDone($allow_done)
487
->setObjectOwnerPHID($this->getObjectOwnerPHID());
488
}
489
490
491
/**
492
* Build links which users can click to show more context in a changeset.
493
*
494
* @param int Beginning of the line range to build links for.
495
* @param int Length of the line range to build links for.
496
* @param int Total number of lines in the changeset.
497
* @return markup Rendered links.
498
*/
499
protected function renderShowContextLinks(
500
$top,
501
$len,
502
$changeset_length,
503
$is_blocks = false) {
504
505
$block_size = 20;
506
$end = ($top + $len) - $block_size;
507
508
// If this is a large block, such that the "top" and "bottom" ranges are
509
// non-overlapping, we'll provide options to show the top, bottom or entire
510
// block. For smaller blocks, we only provide an option to show the entire
511
// block, since it would be silly to show the bottom 20 lines of a 25-line
512
// block.
513
$is_large_block = ($len > ($block_size * 2));
514
515
$links = array();
516
517
$block_display = new PhutilNumber($block_size);
518
519
if ($is_large_block) {
520
$is_first_block = ($top == 0);
521
if ($is_first_block) {
522
if ($is_blocks) {
523
$text = pht('Show First %s Block(s)', $block_display);
524
} else {
525
$text = pht('Show First %s Line(s)', $block_display);
526
}
527
} else {
528
if ($is_blocks) {
529
$text = pht("\xE2\x96\xB2 Show %s Block(s)", $block_display);
530
} else {
531
$text = pht("\xE2\x96\xB2 Show %s Line(s)", $block_display);
532
}
533
}
534
535
$links[] = $this->renderShowContextLink(
536
false,
537
"{$top}-{$len}/{$top}-20",
538
$text);
539
}
540
541
if ($is_blocks) {
542
$text = pht('Show All %s Block(s)', new PhutilNumber($len));
543
} else {
544
$text = pht('Show All %s Line(s)', new PhutilNumber($len));
545
}
546
547
$links[] = $this->renderShowContextLink(
548
true,
549
"{$top}-{$len}/{$top}-{$len}",
550
$text);
551
552
if ($is_large_block) {
553
$is_last_block = (($top + $len) >= $changeset_length);
554
if ($is_last_block) {
555
if ($is_blocks) {
556
$text = pht('Show Last %s Block(s)', $block_display);
557
} else {
558
$text = pht('Show Last %s Line(s)', $block_display);
559
}
560
} else {
561
if ($is_blocks) {
562
$text = pht("\xE2\x96\xBC Show %s Block(s)", $block_display);
563
} else {
564
$text = pht("\xE2\x96\xBC Show %s Line(s)", $block_display);
565
}
566
}
567
568
$links[] = $this->renderShowContextLink(
569
false,
570
"{$top}-{$len}/{$end}-20",
571
$text);
572
}
573
574
return phutil_implode_html(" \xE2\x80\xA2 ", $links);
575
}
576
577
578
/**
579
* Build a link that shows more context in a changeset.
580
*
581
* See @{method:renderShowContextLinks}.
582
*
583
* @param bool Does this link show all context when clicked?
584
* @param string Range specification for lines to show.
585
* @param string Text of the link.
586
* @return markup Rendered link.
587
*/
588
private function renderShowContextLink($is_all, $range, $text) {
589
$reference = $this->getRenderingReference();
590
591
return javelin_tag(
592
'a',
593
array(
594
'href' => '#',
595
'mustcapture' => true,
596
'sigil' => 'show-more',
597
'meta' => array(
598
'type' => ($is_all ? 'all' : null),
599
'range' => $range,
600
),
601
),
602
$text);
603
}
604
605
/**
606
* Build the prefixes for line IDs used to track inline comments.
607
*
608
* @return pair<wild, wild> Left and right prefixes.
609
*/
610
protected function getLineIDPrefixes() {
611
// These look like "C123NL45", which means the line is line 45 on the
612
// "new" side of the file in changeset 123.
613
614
// The "C" stands for "changeset", and is followed by a changeset ID.
615
616
// "N" stands for "new" and means the comment should attach to the new file
617
// when stored. "O" stands for "old" and means the comment should attach to
618
// the old file. These are important because either the old or new part
619
// of a file may appear on the left or right side of the diff in the
620
// diff-of-diffs view.
621
622
// The "L" stands for "line" and is followed by the line number.
623
624
if ($this->getOldChangesetID()) {
625
$left_prefix = array();
626
$left_prefix[] = 'C';
627
$left_prefix[] = $this->getOldChangesetID();
628
$left_prefix[] = $this->getOldAttachesToNewFile() ? 'N' : 'O';
629
$left_prefix[] = 'L';
630
$left_prefix = implode('', $left_prefix);
631
} else {
632
$left_prefix = null;
633
}
634
635
if ($this->getNewChangesetID()) {
636
$right_prefix = array();
637
$right_prefix[] = 'C';
638
$right_prefix[] = $this->getNewChangesetID();
639
$right_prefix[] = $this->getNewAttachesToNewFile() ? 'N' : 'O';
640
$right_prefix[] = 'L';
641
$right_prefix = implode('', $right_prefix);
642
} else {
643
$right_prefix = null;
644
}
645
646
return array($left_prefix, $right_prefix);
647
}
648
649
}
650
651