@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
3abstract class PhabricatorDocumentEngine
4 extends Phobject {
5
6 private $viewer;
7 private $highlightedLines = array();
8 private $encodingConfiguration;
9 private $highlightingConfiguration;
10 private $blameConfiguration = true;
11
12 final public function setViewer(PhabricatorUser $viewer) {
13 $this->viewer = $viewer;
14 return $this;
15 }
16
17 final public function getViewer() {
18 return $this->viewer;
19 }
20
21 final public function setHighlightedLines(array $highlighted_lines) {
22 $this->highlightedLines = $highlighted_lines;
23 return $this;
24 }
25
26 final public function getHighlightedLines() {
27 return $this->highlightedLines;
28 }
29
30 final public function canRenderDocument(PhabricatorDocumentRef $ref) {
31 try {
32 return $this->canRenderDocumentType($ref);
33 } catch (Throwable $e) {
34 phlog($e);
35 return false;
36 }
37 }
38
39 public function canDiffDocuments(
40 ?PhabricatorDocumentRef $uref = null,
41 ?PhabricatorDocumentRef $vref = null) {
42 return false;
43 }
44
45 public function newBlockDiffViews(
46 PhabricatorDocumentRef $uref,
47 PhabricatorDocumentEngineBlock $ublock,
48 PhabricatorDocumentRef $vref,
49 PhabricatorDocumentEngineBlock $vblock) {
50
51 $u_content = $this->newBlockContentView($uref, $ublock);
52 $v_content = $this->newBlockContentView($vref, $vblock);
53
54 return id(new PhabricatorDocumentEngineBlockDiff())
55 ->setOldContent($u_content)
56 ->addOldClass('old')
57 ->addOldClass('old-full')
58 ->setNewContent($v_content)
59 ->addNewClass('new')
60 ->addNewClass('new-full');
61 }
62
63 public function newBlockContentView(
64 PhabricatorDocumentRef $ref,
65 PhabricatorDocumentEngineBlock $block) {
66 return $block->getContent();
67 }
68
69 public function newEngineBlocks(
70 PhabricatorDocumentRef $uref,
71 PhabricatorDocumentRef $vref) {
72 throw new PhutilMethodNotImplementedException();
73 }
74
75 public function canConfigureEncoding(PhabricatorDocumentRef $ref) {
76 return false;
77 }
78
79 public function canConfigureHighlighting(PhabricatorDocumentRef $ref) {
80 return false;
81 }
82
83 /**
84 * Check if the engine supports the blame feature.
85 * @return bool
86 */
87 public function canBlame(PhabricatorDocumentRef $ref) {
88 return false;
89 }
90
91 final public function setEncodingConfiguration($config) {
92 $this->encodingConfiguration = $config;
93 return $this;
94 }
95
96 final public function getEncodingConfiguration() {
97 return $this->encodingConfiguration;
98 }
99
100 final public function setHighlightingConfiguration($config) {
101 $this->highlightingConfiguration = $config;
102 return $this;
103 }
104
105 final public function getHighlightingConfiguration() {
106 return $this->highlightingConfiguration;
107 }
108
109 final public function setBlameConfiguration($blame_configuration) {
110 $this->blameConfiguration = $blame_configuration;
111 return $this;
112 }
113
114 final public function getBlameConfiguration() {
115 return $this->blameConfiguration;
116 }
117
118 final protected function getBlameEnabled() {
119 return $this->blameConfiguration;
120 }
121
122 public function shouldRenderAsync(PhabricatorDocumentRef $ref) {
123 return false;
124 }
125
126 abstract protected function canRenderDocumentType(
127 PhabricatorDocumentRef $ref);
128
129 final public function newDocument(PhabricatorDocumentRef $ref) {
130 $can_complete = $this->canRenderCompleteDocument($ref);
131 $can_partial = $this->canRenderPartialDocument($ref);
132
133 if (!$can_complete && !$can_partial) {
134 return $this->newMessage(
135 pht(
136 'This document is too large to be rendered inline. (The document '.
137 'is %s bytes, the limit for this engine is %s bytes.)',
138 new PhutilNumber($ref->getByteLength()),
139 new PhutilNumber($this->getByteLengthLimit())));
140 }
141
142 return $this->newDocumentContent($ref);
143 }
144
145 final public function newDocumentIcon(PhabricatorDocumentRef $ref) {
146 return id(new PHUIIconView())
147 ->setIcon($this->getDocumentIconIcon($ref));
148 }
149
150 abstract protected function newDocumentContent(
151 PhabricatorDocumentRef $ref);
152
153 protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
154 return 'fa-file-o';
155 }
156
157 protected function getDocumentRenderingText(PhabricatorDocumentRef $ref) {
158 return pht('Loading...');
159 }
160
161 final public function getDocumentEngineKey() {
162 return $this->getPhobjectClassConstant('ENGINEKEY');
163 }
164
165 final public static function getAllEngines() {
166 return id(new PhutilClassMapQuery())
167 ->setAncestorClass(self::class)
168 ->setUniqueMethod('getDocumentEngineKey')
169 ->execute();
170 }
171
172 final public function newSortVector(PhabricatorDocumentRef $ref) {
173 $content_score = $this->getContentScore($ref);
174
175 // Prefer engines which can render the entire file over engines which
176 // can only render a header, and engines which can render a header over
177 // engines which can't render anything.
178 if ($this->canRenderCompleteDocument($ref)) {
179 $limit_score = 0;
180 } else if ($this->canRenderPartialDocument($ref)) {
181 $limit_score = 1;
182 } else {
183 $limit_score = 2;
184 }
185
186 return id(new PhutilSortVector())
187 ->addInt($limit_score)
188 ->addInt(-$content_score);
189 }
190
191 protected function getContentScore(PhabricatorDocumentRef $ref) {
192 return 2000;
193 }
194
195 abstract public function getViewAsLabel(PhabricatorDocumentRef $ref);
196
197 public function getViewAsIconIcon(PhabricatorDocumentRef $ref) {
198 $can_complete = $this->canRenderCompleteDocument($ref);
199 $can_partial = $this->canRenderPartialDocument($ref);
200
201 if (!$can_complete && !$can_partial) {
202 return 'fa-times';
203 }
204
205 return $this->getDocumentIconIcon($ref);
206 }
207
208 public function getViewAsIconColor(PhabricatorDocumentRef $ref) {
209 $can_complete = $this->canRenderCompleteDocument($ref);
210
211 if (!$can_complete) {
212 return 'grey';
213 }
214
215 return null;
216 }
217
218 final public static function getEnginesForRef(
219 PhabricatorUser $viewer,
220 PhabricatorDocumentRef $ref) {
221 $engines = self::getAllEngines();
222
223 foreach ($engines as $key => $engine) {
224 $engine = id(clone $engine)
225 ->setViewer($viewer);
226
227 if (!$engine->canRenderDocument($ref)) {
228 unset($engines[$key]);
229 continue;
230 }
231
232 $engines[$key] = $engine;
233 }
234
235 if (!$engines) {
236 throw new Exception(pht('No content engine can render this document.'));
237 }
238
239 $vectors = array();
240 foreach ($engines as $key => $usable_engine) {
241 $vectors[$key] = $usable_engine->newSortVector($ref);
242 }
243 $vectors = msortv($vectors, 'getSelf');
244
245 return array_select_keys($engines, array_keys($vectors));
246 }
247
248 protected function getByteLengthLimit() {
249 return (1024 * 1024 * 8);
250 }
251
252 protected function canRenderCompleteDocument(PhabricatorDocumentRef $ref) {
253 $limit = $this->getByteLengthLimit();
254 if ($limit) {
255 $length = $ref->getByteLength();
256 if ($length > $limit) {
257 return false;
258 }
259 }
260
261 return true;
262 }
263
264 protected function canRenderPartialDocument(PhabricatorDocumentRef $ref) {
265 return false;
266 }
267
268 protected function newMessage($message) {
269 return phutil_tag(
270 'div',
271 array(
272 'class' => 'document-engine-error',
273 ),
274 $message);
275 }
276
277 final public function newLoadingContent(PhabricatorDocumentRef $ref) {
278 $spinner = id(new PHUIIconView())
279 ->setIcon('fa-gear')
280 ->addClass('ph-spin');
281
282 return phutil_tag(
283 'div',
284 array(
285 'class' => 'document-engine-loading',
286 ),
287 array(
288 $spinner,
289 $this->getDocumentRenderingText($ref),
290 ));
291 }
292
293 public function shouldSuggestEngine(PhabricatorDocumentRef $ref) {
294 return false;
295 }
296
297}