@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
3final class PhabricatorMetaMTAMailViewController
4 extends PhabricatorMetaMTAController {
5
6 public function handleRequest(AphrontRequest $request) {
7 $viewer = $this->getViewer();
8
9 $mail = id(new PhabricatorMetaMTAMailQuery())
10 ->setViewer($viewer)
11 ->withIDs(array($request->getURIData('id')))
12 ->executeOne();
13 if (!$mail) {
14 return new Aphront404Response();
15 }
16
17 if ($mail->hasSensitiveContent()) {
18 $title = pht('Content Redacted');
19 } else {
20 $title = $mail->getSubject();
21 }
22
23 $header = id(new PHUIHeaderView())
24 ->setHeader($title)
25 ->setUser($viewer)
26 ->setPolicyObject($mail)
27 ->setHeaderIcon('fa-envelope');
28
29 $status = $mail->getStatus();
30 $name = PhabricatorMailOutboundStatus::getStatusName($status);
31 $icon = PhabricatorMailOutboundStatus::getStatusIcon($status);
32 $color = PhabricatorMailOutboundStatus::getStatusColor($status);
33 $header->setStatus($icon, $color, $name);
34
35 if ($mail->getMustEncrypt()) {
36 Javelin::initBehavior('phabricator-tooltips');
37 $header->addTag(
38 id(new PHUITagView())
39 ->setType(PHUITagView::TYPE_SHADE)
40 ->setColor('blue')
41 ->setName(pht('Must Encrypt'))
42 ->setIcon('fa-shield blue')
43 ->addSigil('has-tooltip')
44 ->setMetadata(
45 array(
46 'tip' => pht(
47 'Message content can only be transmitted over secure '.
48 'channels.'),
49 )));
50 }
51
52 $crumbs = $this->buildApplicationCrumbs()
53 ->addTextCrumb(pht('Mail %d', $mail->getID()))
54 ->setBorder(true);
55
56 $tab_group = id(new PHUITabGroupView())
57 ->addTab(
58 id(new PHUITabView())
59 ->setName(pht('Message'))
60 ->setKey('message')
61 ->appendChild($this->buildMessageProperties($mail)))
62 ->addTab(
63 id(new PHUITabView())
64 ->setName(pht('Headers'))
65 ->setKey('headers')
66 ->appendChild($this->buildHeaderProperties($mail)))
67 ->addTab(
68 id(new PHUITabView())
69 ->setName(pht('Delivery'))
70 ->setKey('delivery')
71 ->appendChild($this->buildDeliveryProperties($mail)))
72 ->addTab(
73 id(new PHUITabView())
74 ->setName(pht('Metadata'))
75 ->setKey('metadata')
76 ->appendChild($this->buildMetadataProperties($mail)));
77
78 $header_view = id(new PHUIHeaderView())
79 ->setHeader(pht('Mail'));
80
81 $object_phid = $mail->getRelatedPHID();
82 if ($object_phid) {
83 $handles = $viewer->loadHandles(array($object_phid));
84 $handle = $handles[$object_phid];
85 if ($handle->isComplete() && $handle->getURI()) {
86 $view_button = id(new PHUIButtonView())
87 ->setTag('a')
88 ->setText(pht('View Object'))
89 ->setIcon('fa-chevron-right')
90 ->setHref($handle->getURI());
91
92 $header_view->addActionLink($view_button);
93 }
94 }
95
96 $object_box = id(new PHUIObjectBoxView())
97 ->setHeader($header_view)
98 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
99 ->addTabGroup($tab_group);
100
101 $view = id(new PHUITwoColumnView())
102 ->setHeader($header)
103 ->setFooter($object_box);
104
105 return $this->newPage()
106 ->setTitle($title)
107 ->setCrumbs($crumbs)
108 ->setPageObjectPHIDs(array($mail->getPHID()))
109 ->appendChild($view);
110 }
111
112 private function buildMessageProperties(PhabricatorMetaMTAMail $mail) {
113 $viewer = $this->getViewer();
114
115 $properties = id(new PHUIPropertyListView())
116 ->setUser($viewer)
117 ->setObject($mail);
118
119 if ($mail->getFrom()) {
120 $from_str = $viewer->renderHandle($mail->getFrom());
121 } else {
122 $from_str = pht(
123 'Sent by %s',
124 PlatformSymbols::getPlatformServerName());
125 }
126 $properties->addProperty(
127 pht('From'),
128 $from_str);
129
130 if ($mail->getToPHIDs()) {
131 $to_list = $viewer->renderHandleList($mail->getToPHIDs());
132 } else {
133 $to_list = pht('None');
134 }
135 $properties->addProperty(
136 pht('To'),
137 $to_list);
138
139 if ($mail->getCcPHIDs()) {
140 $cc_list = $viewer->renderHandleList($mail->getCcPHIDs());
141 } else {
142 $cc_list = pht('None');
143 }
144 $properties->addProperty(
145 pht('Cc'),
146 $cc_list);
147
148 $properties->addProperty(
149 pht('Sent'),
150 phabricator_datetime($mail->getDateCreated(), $viewer));
151
152 $properties->addSectionHeader(
153 pht('Message'),
154 PHUIPropertyListView::ICON_SUMMARY);
155
156 if ($mail->hasSensitiveContent()) {
157 $body = phutil_tag(
158 'em',
159 array(),
160 pht(
161 'The content of this mail is sensitive and it can not be '.
162 'viewed from the web UI.'));
163 } else {
164 $body = phutil_tag(
165 'div',
166 array(
167 'style' => 'white-space: pre-wrap',
168 ),
169 $mail->getBody());
170 }
171
172 $properties->addTextContent($body);
173
174 $file_phids = $mail->getAttachmentFilePHIDs();
175 if ($file_phids) {
176 $properties->addProperty(
177 pht('Attached Files'),
178 $viewer->loadHandles($file_phids)->renderList());
179 }
180
181 return $properties;
182 }
183
184 private function buildHeaderProperties(PhabricatorMetaMTAMail $mail) {
185 $viewer = $this->getViewer();
186
187 $properties = id(new PHUIPropertyListView())
188 ->setUser($viewer)
189 ->setStacked(true);
190
191 $headers = $mail->getDeliveredHeaders();
192 if (!$headers) {
193 $headers = array();
194 }
195
196 // Sort headers by name.
197 $headers = isort($headers, '0');
198
199 foreach ($headers as $header) {
200 list($key, $value) = $header;
201 $properties->addProperty($key, $value);
202 }
203
204 $encrypt_phids = $mail->getMustEncryptReasons();
205 if ($encrypt_phids) {
206 $properties->addProperty(
207 pht('Must Encrypt'),
208 $viewer->loadHandles($encrypt_phids)
209 ->renderList());
210 }
211
212
213 return $properties;
214 }
215
216 private function buildDeliveryProperties(PhabricatorMetaMTAMail $mail) {
217 $viewer = $this->getViewer();
218
219 $properties = id(new PHUIPropertyListView())
220 ->setUser($viewer);
221
222 $actors = $mail->getDeliveredActors();
223 $reasons = null;
224 if (!$actors) {
225 if ($mail->getStatus() == PhabricatorMailOutboundStatus::STATUS_QUEUE) {
226 $delivery = $this->renderEmptyMessage(
227 pht(
228 'This message has not been delivered yet, so delivery information '.
229 'is not available.'));
230 } else {
231 $delivery = $this->renderEmptyMessage(
232 pht(
233 'This is an older message that predates recording delivery '.
234 'information, so none is available.'));
235 }
236 } else {
237 $actor = idx($actors, $viewer->getPHID());
238 if (!$actor) {
239 $delivery = phutil_tag(
240 'em',
241 array(),
242 pht('This message was not delivered to you.'));
243 } else {
244 $deliverable = $actor['deliverable'];
245 if ($deliverable) {
246 $delivery = pht('Delivered');
247 } else {
248 $delivery = pht('Voided');
249 }
250
251 $reasons = new PHUIStatusListView();
252
253 $reason_codes = $actor['reasons'];
254 if (!$reason_codes) {
255 $reason_codes = array(
256 PhabricatorMetaMTAActor::REASON_NONE,
257 );
258 }
259
260 $icon_yes = 'fa-check green';
261 $icon_no = 'fa-times red';
262
263 foreach ($reason_codes as $reason) {
264 $target = phutil_tag(
265 'strong',
266 array(),
267 PhabricatorMetaMTAActor::getReasonName($reason));
268
269 if (PhabricatorMetaMTAActor::isDeliveryReason($reason)) {
270 $icon = $icon_yes;
271 } else {
272 $icon = $icon_no;
273 }
274
275 $item = id(new PHUIStatusItemView())
276 ->setIcon($icon)
277 ->setTarget($target)
278 ->setNote(PhabricatorMetaMTAActor::getReasonDescription($reason));
279
280 $reasons->addItem($item);
281 }
282 }
283 }
284
285 $properties->addProperty(pht('Delivery'), $delivery);
286 if ($reasons) {
287 $properties->addProperty(pht('Reasons'), $reasons);
288 $properties->addProperty(
289 null,
290 $this->renderEmptyMessage(
291 pht(
292 'Delivery reasons are listed from weakest to strongest.')));
293 }
294
295 $properties->addSectionHeader(
296 pht('Routing Rules'), 'fa-paper-plane-o');
297
298 $map = $mail->getDeliveredRoutingMap();
299 $routing_detail = null;
300 if ($map === null) {
301 if ($mail->getStatus() == PhabricatorMailOutboundStatus::STATUS_QUEUE) {
302 $routing_result = $this->renderEmptyMessage(
303 pht(
304 'This message has not been sent yet, so routing rules have '.
305 'not been computed.'));
306 } else {
307 $routing_result = $this->renderEmptyMessage(
308 pht(
309 'This is an older message which predates routing rules.'));
310 }
311 } else {
312 $rule = idx($map, $viewer->getPHID());
313 if ($rule === null) {
314 $rule = idx($map, 'default');
315 }
316
317 if ($rule === null) {
318 $routing_result = $this->renderEmptyMessage(
319 pht(
320 'No routing rules applied when delivering this message to you.'));
321 } else {
322 $rule_const = $rule['rule'];
323 $reason_phid = $rule['reason'];
324 switch ($rule_const) {
325 case PhabricatorMailRoutingRule::ROUTE_AS_NOTIFICATION:
326 $routing_result = pht(
327 'This message was routed as a notification because it '.
328 'matched %s.',
329 $viewer->renderHandle($reason_phid)->render());
330 break;
331 case PhabricatorMailRoutingRule::ROUTE_AS_MAIL:
332 $routing_result = pht(
333 'This message was routed as an email because it matched %s.',
334 $viewer->renderHandle($reason_phid)->render());
335 break;
336 default:
337 $routing_result = pht('Unknown routing rule "%s".', $rule_const);
338 break;
339 }
340 }
341
342 $routing_rules = $mail->getDeliveredRoutingRules();
343 if ($routing_rules) {
344 $rules = array();
345 foreach ($routing_rules as $rule) {
346 $phids = idx($rule, 'phids');
347 if ($phids === null) {
348 $rules[] = $rule;
349 } else if (in_array($viewer->getPHID(), $phids)) {
350 $rules[] = $rule;
351 }
352 }
353
354 // Reorder rules by strength.
355 foreach ($rules as $key => $rule) {
356 $const = $rule['routingRule'];
357 $phids = $rule['phids'];
358
359 if ($phids === null) {
360 $type = 'A';
361 } else {
362 $type = 'B';
363 }
364
365 $rules[$key]['strength'] = sprintf(
366 '~%s%08d',
367 $type,
368 PhabricatorMailRoutingRule::getRuleStrength($const));
369 }
370 $rules = isort($rules, 'strength');
371
372 $routing_detail = new PHUIStatusListView();
373 foreach ($rules as $rule) {
374 $const = $rule['routingRule'];
375 $phids = $rule['phids'];
376
377 $name = PhabricatorMailRoutingRule::getRuleName($const);
378
379 $icon = PhabricatorMailRoutingRule::getRuleIcon($const);
380 $color = PhabricatorMailRoutingRule::getRuleColor($const);
381
382 if ($phids === null) {
383 $kind = pht('Global');
384 } else {
385 $kind = pht('Personal');
386 }
387
388 $target = array($kind, ': ', $name);
389 $target = phutil_tag('strong', array(), $target);
390
391 $item = id(new PHUIStatusItemView())
392 ->setTarget($target)
393 ->setNote($viewer->renderHandle($rule['reasonPHID']))
394 ->setIcon($icon, $color);
395
396 $routing_detail->addItem($item);
397 }
398 }
399 }
400
401 $properties->addProperty(pht('Effective Rule'), $routing_result);
402
403 if ($routing_detail !== null) {
404 $properties->addProperty(pht('All Matching Rules'), $routing_detail);
405 $properties->addProperty(
406 null,
407 $this->renderEmptyMessage(
408 pht(
409 'Matching rules are listed from weakest to strongest.')));
410 }
411
412 return $properties;
413 }
414
415 private function buildMetadataProperties(PhabricatorMetaMTAMail $mail) {
416 $viewer = $this->getViewer();
417
418 $properties = id(new PHUIPropertyListView())
419 ->setUser($viewer);
420
421 $properties->addProperty(pht('Message PHID'), $mail->getPHID());
422
423 $details = $mail->getMessage();
424 if (!phutil_nonempty_string($details)) {
425 $details = phutil_tag('em', array(), pht('None'));
426 }
427 $properties->addProperty(pht('Status Details'), $details);
428
429 $actor_phid = $mail->getActorPHID();
430 if ($actor_phid) {
431 $actor_str = $viewer->renderHandle($actor_phid);
432 } else {
433 $actor_str = pht('Generated by Server');
434 }
435 $properties->addProperty(pht('Actor'), $actor_str);
436
437 $related_phid = $mail->getRelatedPHID();
438 if ($related_phid) {
439 $related = $viewer->renderHandle($mail->getRelatedPHID());
440 } else {
441 $related = phutil_tag('em', array(), pht('None'));
442 }
443 $properties->addProperty(pht('Related Object'), $related);
444
445 return $properties;
446 }
447
448 private function renderEmptyMessage($message) {
449 return phutil_tag('em', array(), $message);
450 }
451
452}