Page MenuHomePhabricator

D13878.id33512.diff
No OneTemporary

D13878.id33512.diff

diff --git a/src/applications/metamta/application/PhabricatorMetaMTAApplication.php b/src/applications/metamta/application/PhabricatorMetaMTAApplication.php
--- a/src/applications/metamta/application/PhabricatorMetaMTAApplication.php
+++ b/src/applications/metamta/application/PhabricatorMetaMTAApplication.php
@@ -3,7 +3,7 @@
final class PhabricatorMetaMTAApplication extends PhabricatorApplication {
public function getName() {
- return pht('MetaMTA');
+ return pht('Mail');
}
public function getBaseURI() {
@@ -15,11 +15,11 @@
}
public function getShortDescription() {
- return pht('Delivers Mail');
+ return pht('Send and Receive Mail');
}
public function getFlavorText() {
- return pht('Yo dawg, we heard you like MTAs.');
+ return pht('Every program attempts to expand until it can read mail.');
}
public function getApplicationGroup() {
@@ -30,12 +30,8 @@
return false;
}
- public function isLaunchable() {
- return false;
- }
-
public function getTypeaheadURI() {
- return null;
+ return '/mail/';
}
public function getRoutes() {
diff --git a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php
--- a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php
+++ b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php
@@ -4,7 +4,7 @@
extends PhabricatorMetaMTAController {
public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getUser();
+ $viewer = $this->getViewer();
$mail = id(new PhabricatorMetaMTAMailQuery())
->setViewer($viewer)
@@ -19,17 +19,51 @@
} else {
$title = $mail->getSubject();
}
+
$header = id(new PHUIHeaderView())
->setHeader($title)
- ->setUser($this->getRequest()->getUser())
+ ->setUser($viewer)
->setPolicyObject($mail);
+ switch ($mail->getStatus()) {
+ case PhabricatorMetaMTAMail::STATUS_QUEUE:
+ $icon = 'fa-clock-o';
+ $color = 'blue';
+ $name = pht('Queued');
+ break;
+ case PhabricatorMetaMTAMail::STATUS_SENT:
+ $icon = 'fa-envelope';
+ $color = 'green';
+ $name = pht('Sent');
+ break;
+ case PhabricatorMetaMTAMail::STATUS_FAIL:
+ $icon = 'fa-envelope';
+ $color = 'red';
+ $name = pht('Delivery Failed');
+ break;
+ case PhabricatorMetaMTAMail::STATUS_VOID:
+ $icon = 'fa-envelope';
+ $color = 'black';
+ $name = pht('Voided');
+ break;
+ default:
+ $icon = 'fa-question-circle';
+ $color = 'yellow';
+ $name = pht('Unknown');
+ break;
+ }
+
+ $header->setStatus($icon, $color, $name);
+
$crumbs = $this->buildApplicationCrumbs()
- ->addTextCrumb(
- 'Mail '.$mail->getID());
+ ->addTextCrumb(pht('Mail %d', $mail->getID()));
+
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
- ->addPropertyList($this->buildPropertyView($mail));
+ ->addPropertyList($this->buildMessageProperties($mail), pht('Message'))
+ ->addPropertyList($this->buildHeaderProperties($mail), pht('Headers'))
+ ->addPropertyList($this->buildDeliveryProperties($mail), pht('Delivery'))
+ ->addPropertyList($this->buildMetadataProperties($mail), pht('Metadata'));
return $this->buildApplicationPage(
array(
@@ -42,42 +76,13 @@
));
}
- private function buildPropertyView(PhabricatorMetaMTAMail $mail) {
+ private function buildMessageProperties(PhabricatorMetaMTAMail $mail) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($mail);
- $properties->addProperty(
- pht('ID'),
- $mail->getID());
-
- $properties->addProperty(
- pht('Status'),
- $mail->getStatus());
-
- if ($mail->getMessage()) {
- $properties->addProperty(
- pht('Status Details'),
- $mail->getMessage());
- }
-
- if ($mail->getRelatedPHID()) {
- $properties->addProperty(
- pht('Related Object'),
- $viewer->renderHandle($mail->getRelatedPHID()));
- }
-
- if ($mail->getActorPHID()) {
- $actor_str = $viewer->renderHandle($mail->getActorPHID());
- } else {
- $actor_str = pht('Generated by Phabricator');
- }
- $properties->addProperty(
- pht('Actor'),
- $actor_str);
-
if ($mail->getFrom()) {
$from_str = $viewer->renderHandle($mail->getFrom());
} else {
@@ -105,6 +110,167 @@
pht('Cc'),
$cc_list);
+ $properties->addSectionHeader(
+ pht('Message'),
+ PHUIPropertyListView::ICON_SUMMARY);
+
+ if ($mail->hasSensitiveContent()) {
+ $body = phutil_tag(
+ 'em',
+ array(),
+ pht(
+ 'The content of this mail is sensitive and it can not be '.
+ 'viewed from the web UI.'));
+ } else {
+ $body = phutil_tag(
+ 'div',
+ array(
+ 'style' => 'white-space: pre-wrap',
+ ),
+ $mail->getBody());
+ }
+
+ $properties->addTextContent($body);
+
+
+ return $properties;
+ }
+
+ private function buildHeaderProperties(PhabricatorMetaMTAMail $mail) {
+ $viewer = $this->getViewer();
+
+ $properties = id(new PHUIPropertyListView())
+ ->setUser($viewer)
+ ->setStacked(true);
+
+ $headers = $mail->getDeliveredHeaders();
+ if ($headers === null) {
+ $headers = $mail->generateHeaders();
+ }
+
+ // Sort headers by name.
+ $headers = isort($headers, 0);
+
+ foreach ($headers as $header) {
+ list($key, $value) = $header;
+ $properties->addProperty($key, $value);
+ }
+
+ return $properties;
+ }
+
+ private function buildDeliveryProperties(PhabricatorMetaMTAMail $mail) {
+ $viewer = $this->getViewer();
+
+ $properties = id(new PHUIPropertyListView())
+ ->setUser($viewer);
+
+ $actors = $mail->getDeliveredActors();
+ $reasons = null;
+ if (!$actors) {
+ // TODO: We can get rid of this special-cased message after these changes
+ // have been live for a while, but provide a more tailored message for
+ // now so things are a little less confusing for users.
+ if ($mail->getStatus() == PhabricatorMetaMTAMail::STATUS_SENT) {
+ $delivery = phutil_tag(
+ 'em',
+ array(),
+ pht(
+ 'This is an older message that predates recording delivery '.
+ 'information, so none is available.'));
+ } else {
+ $delivery = phutil_tag(
+ 'em',
+ array(),
+ pht(
+ 'This message has not been delivered yet, so delivery information '.
+ 'is not available.'));
+ }
+ } else {
+ $actor = idx($actors, $viewer->getPHID());
+ if (!$actor) {
+ $delivery = phutil_tag(
+ 'em',
+ array(),
+ pht('This message was not delivered to you.'));
+ } else {
+ $deliverable = $actor['deliverable'];
+ if ($deliverable) {
+ $delivery = pht('Delivered');
+ } else {
+ $delivery = pht('Voided');
+ }
+
+ $reasons = id(new PHUIStatusListView());
+
+ $reason_codes = $actor['reasons'];
+ if (!$reason_codes) {
+ $reason_codes = array(
+ PhabricatorMetaMTAActor::REASON_NONE,
+ );
+ }
+
+ $icon_yes = 'fa-check green';
+ $icon_no = 'fa-times red';
+
+ foreach ($reason_codes as $reason) {
+ $target = phutil_tag(
+ 'strong',
+ array(),
+ PhabricatorMetaMTAActor::getReasonName($reason));
+
+ if (PhabricatorMetaMTAActor::isDeliveryReason($reason)) {
+ $icon = $icon_yes;
+ } else {
+ $icon = $icon_no;
+ }
+
+ $item = id(new PHUIStatusItemView())
+ ->setIcon($icon)
+ ->setTarget($target)
+ ->setNote(PhabricatorMetaMTAActor::getReasonDescription($reason));
+
+ $reasons->addItem($item);
+ }
+ }
+ }
+
+ $properties->addProperty(pht('Delivery'), $delivery);
+ if ($reasons) {
+ $properties->addProperty(pht('Reasons'), $reasons);
+ }
+
+ return $properties;
+ }
+
+ private function buildMetadataProperties(PhabricatorMetaMTAMail $mail) {
+ $viewer = $this->getViewer();
+
+ $properties = id(new PHUIPropertyListView())
+ ->setUser($viewer);
+
+ $details = $mail->getMessage();
+ if (!strlen($details)) {
+ $details = phutil_tag('em', array(), pht('None'));
+ }
+ $properties->addProperty(pht('Status Details'), $details);
+
+ $actor_phid = $mail->getActorPHID();
+ if ($actor_phid) {
+ $actor_str = $viewer->renderHandle($actor_phid);
+ } else {
+ $actor_str = pht('Generated by Phabricator');
+ }
+ $properties->addProperty(pht('Actor'), $actor_str);
+
+ $related_phid = $mail->getRelatedPHID();
+ if ($related_phid) {
+ $related = $viewer->renderHandle($mail->getRelatedPHID());
+ } else {
+ $related = phutil_tag('em', array(), pht('None'));
+ }
+ $properties->addProperty(pht('Related Object'), $related);
+
return $properties;
}
diff --git a/src/applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php b/src/applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php
--- a/src/applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php
+++ b/src/applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php
@@ -76,23 +76,20 @@
$info[] = pht('Related PHID: %s', $message->getRelatedPHID());
$info[] = pht('Message: %s', $message->getMessage());
+ $ignore = array(
+ 'body' => true,
+ 'html-body' => true,
+ 'headers' => true,
+ 'attachments' => true,
+ 'headers.sent' => true,
+ 'authors.sent' => true,
+ );
+
$info[] = null;
$info[] = pht('PARAMETERS');
$parameters = $message->getParameters();
foreach ($parameters as $key => $value) {
- if ($key == 'body') {
- continue;
- }
-
- if ($key == 'html-body') {
- continue;
- }
-
- if ($key == 'headers') {
- continue;
- }
-
- if ($key == 'attachments') {
+ if (isset($ignore[$key])) {
continue;
}
@@ -105,7 +102,13 @@
$info[] = null;
$info[] = pht('HEADERS');
- foreach (idx($parameters, 'headers', array()) as $header) {
+
+ $headers = $message->getDeliveredHeaders();
+ if (!$headers) {
+ $headers = $message->generateHeaders();
+ }
+
+ foreach ($headers as $header) {
list($name, $value) = $header;
$info[] = "{$name}: {$value}";
}
@@ -119,21 +122,33 @@
}
}
- $actors = $message->loadAllActors();
- $actors = array_select_keys(
- $actors,
- array_merge($message->getToPHIDs(), $message->getCcPHIDs()));
- $info[] = null;
- $info[] = pht('RECIPIENTS');
- foreach ($actors as $actor) {
- if ($actor->isDeliverable()) {
- $info[] = ' '.coalesce($actor->getName(), $actor->getPHID());
- } else {
- $info[] = '! '.coalesce($actor->getName(), $actor->getPHID());
- }
- foreach ($actor->getDeliverabilityReasons() as $reason) {
- $desc = PhabricatorMetaMTAActor::getReasonDescription($reason);
- $info[] = ' - '.$desc;
+ $all_actors = $message->loadAllActors();
+
+ $actors = $message->getDeliveredActors();
+ if ($actors) {
+ $info[] = null;
+ $info[] = pht('RECIPIENTS');
+ foreach ($actors as $actor_phid => $actor_info) {
+ $actor = idx($all_actors, $actor_phid);
+ if ($actor) {
+ $actor_name = coalesce($actor->getName(), $actor_phid);
+ } else {
+ $actor_name = $actor_phid;
+ }
+
+ $deliverable = $actor_info['deliverable'];
+ if ($deliverable) {
+ $info[] = ' '.$actor_name;
+ } else {
+ $info[] = '! '.$actor_name;
+ }
+
+ $reasons = $actor_info['reasons'];
+ foreach ($reasons as $reason) {
+ $name = PhabricatorMetaMTAActor::getReasonName($reason);
+ $desc = PhabricatorMetaMTAActor::getReasonDescription($reason);
+ $info[] = ' - '.$name.': '.$desc;
+ }
}
}
diff --git a/src/applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php b/src/applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php
--- a/src/applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php
+++ b/src/applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php
@@ -28,8 +28,11 @@
->execute();
$unfiltered = array();
+ $delivered = array();
foreach ($mails as $mail) {
+ // Count messages we attempted to deliver. This includes messages which
+ // were voided by preferences or other rules.
$unfiltered_actors = mpull($mail->loadAllActors(), 'getPHID');
foreach ($unfiltered_actors as $phid) {
if (empty($unfiltered[$phid])) {
@@ -37,9 +40,26 @@
}
$unfiltered[$phid]++;
}
+
+ // Now, count mail we actually delivered.
+ $result = $mail->getDeliveredActors();
+ if ($result) {
+ foreach ($result as $actor_phid => $actor_info) {
+ if (!$actor_info['deliverable']) {
+ continue;
+ }
+ if (empty($delivered[$actor_phid])) {
+ $delivered[$actor_phid] = 0;
+ }
+ $delivered[$actor_phid]++;
+ }
+ }
}
+ // Sort users by delivered mail, then unfiltered mail.
+ arsort($delivered);
arsort($unfiltered);
+ $delivered = $delivered + array_fill_keys(array_keys($unfiltered), 0);
$table = id(new PhutilConsoleTable())
->setBorders(true)
@@ -52,16 +72,23 @@
'unfiltered',
array(
'title' => pht('Unfiltered'),
+ ))
+ ->addColumn(
+ 'delivered',
+ array(
+ 'title' => pht('Delivered'),
));
$handles = $viewer->loadHandles(array_keys($unfiltered));
$names = mpull(iterator_to_array($handles), 'getName', 'getPHID');
- foreach ($unfiltered as $phid => $count) {
+ foreach ($delivered as $phid => $delivered_count) {
+ $unfiltered_count = idx($unfiltered, $phid, 0);
$table->addRow(
array(
'user' => idx($names, $phid),
- 'unfiltered' => $count,
+ 'unfiltered' => $unfiltered_count,
+ 'delivered' => $delivered_count,
));
}
@@ -70,7 +97,9 @@
echo "\n";
echo pht('Mail sent in the last 30 days.')."\n";
echo pht(
- '"Unfiltered" is raw volume before preferences were applied.')."\n";
+ '"Unfiltered" is raw volume before rules applied.')."\n";
+ echo pht(
+ '"Delivered" shows email actually sent.')."\n";
echo "\n";
return 0;
diff --git a/src/applications/metamta/query/PhabricatorMetaMTAActor.php b/src/applications/metamta/query/PhabricatorMetaMTAActor.php
--- a/src/applications/metamta/query/PhabricatorMetaMTAActor.php
+++ b/src/applications/metamta/query/PhabricatorMetaMTAActor.php
@@ -5,6 +5,7 @@
const STATUS_DELIVERABLE = 'deliverable';
const STATUS_UNDELIVERABLE = 'undeliverable';
+ const REASON_NONE = 'none';
const REASON_UNLOADABLE = 'unloadable';
const REASON_UNMAILABLE = 'unmailable';
const REASON_NO_ADDRESS = 'noaddress';
@@ -71,8 +72,42 @@
return $this->reasons;
}
+ public static function isDeliveryReason($reason) {
+ switch ($reason) {
+ case self::REASON_NONE:
+ case self::REASON_FORCE:
+ case self::REASON_FORCE_HERALD:
+ return true;
+ default:
+ // All other reasons cause the message to not be delivered.
+ return false;
+ }
+ }
+
+ public static function getReasonName($reason) {
+ $names = array(
+ self::REASON_NONE => pht('None'),
+ self::REASON_DISABLED => pht('Disabled Recipient'),
+ self::REASON_BOT => pht('Bot Recipient'),
+ self::REASON_NO_ADDRESS => pht('No Address'),
+ self::REASON_EXTERNAL_TYPE => pht('External Recipient'),
+ self::REASON_UNMAILABLE => pht('Not Mailable'),
+ self::REASON_RESPONSE => pht('Similar Reply'),
+ self::REASON_SELF => pht('Self Mail'),
+ self::REASON_MAIL_DISABLED => pht('Mail Disabled'),
+ self::REASON_MAILTAGS => pht('Mail Tags'),
+ self::REASON_UNLOADABLE => pht('Bad Recipient'),
+ self::REASON_FORCE => pht('Forced Mail'),
+ self::REASON_FORCE_HERALD => pht('Forced by Herald'),
+ );
+
+ return idx($names, $reason, pht('Unknown ("%s")', $reason));
+ }
+
public static function getReasonDescription($reason) {
$descriptions = array(
+ self::REASON_NONE => pht(
+ 'No special rules affected this mail.'),
self::REASON_DISABLED => pht(
'This user is disabled; disabled users do not receive mail.'),
self::REASON_BOT => pht(
diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php
--- a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php
+++ b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php
@@ -436,6 +436,8 @@
}
try {
+ $headers = $this->generateHeaders();
+
$params = $this->parameters;
$actors = $this->loadAllActors();
@@ -535,16 +537,6 @@
$add_cc,
mpull($cc_actors, 'getEmailAddress'));
break;
- case 'headers':
- foreach ($value as $pair) {
- list($header_key, $header_value) = $pair;
-
- // NOTE: If we have \n in a header, SES rejects the email.
- $header_value = str_replace("\n", ' ', $header_value);
-
- $mailer->addHeader($header_key, $header_value);
- }
- break;
case 'attachments':
$value = $this->getAttachments();
foreach ($value as $attachment) {
@@ -593,11 +585,6 @@
$mailer->setSubject(implode(' ', array_filter($subject)));
break;
- case 'is-bulk':
- if ($value) {
- $mailer->addHeader('Precedence', 'bulk');
- }
- break;
case 'thread-id':
// NOTE: Gmail freaks out about In-Reply-To and References which
@@ -608,7 +595,7 @@
$value = '<'.$value.'@'.$domain.'>';
if ($is_first && $mailer->supportsMessageIDHeader()) {
- $mailer->addHeader('Message-ID', $value);
+ $headers[] = array('Message-ID', $value);
} else {
$in_reply_to = $value;
$references = array($value);
@@ -620,21 +607,16 @@
$references[] = $parent_id;
}
$references = implode(' ', $references);
- $mailer->addHeader('In-Reply-To', $in_reply_to);
- $mailer->addHeader('References', $references);
+ $headers[] = array('In-Reply-To', $in_reply_to);
+ $headers[] = array('References', $references);
}
$thread_index = $this->generateThreadIndex($value, $is_first);
- $mailer->addHeader('Thread-Index', $thread_index);
- break;
- case 'mailtags':
- // Handled below.
- break;
- case 'subject-prefix':
- case 'vary-subject-prefix':
- // Handled above.
+ $headers[] = array('Thread-Index', $thread_index);
break;
default:
- // Just discard.
+ // Other parameters are handled elsewhere or are not relevant to
+ // constructing the message.
+ break;
}
}
@@ -660,6 +642,25 @@
$mailer->setHTMLBody($params['html-body']);
}
+ // Pass the headers to the mailer, then save the state so we can show
+ // them in the web UI.
+ foreach ($headers as $header) {
+ list($header_key, $header_value) = $header;
+ $mailer->addHeader($header_key, $header_value);
+ }
+ $this->setParam('headers.sent', $headers);
+
+ // Save the final deliverability outcomes and reasoning so we can
+ // explain why things happened the way they did.
+ $actor_list = array();
+ foreach ($actors as $actor) {
+ $actor_list[$actor->getPHID()] = array(
+ 'deliverable' => $actor->isDeliverable(),
+ 'reasons' => $actor->getDeliverabilityReasons(),
+ );
+ }
+ $this->setParam('actors.sent', $actor_list);
+
if (!$add_to && !$add_cc) {
$this->setStatus(self::STATUS_VOID);
$this->setMessage(
@@ -692,24 +693,6 @@
return $this->save();
}
- $mailer->addHeader('X-Phabricator-Sent-This-Message', 'Yes');
- $mailer->addHeader('X-Mail-Transport-Agent', 'MetaMTA');
-
- // Some clients respect this to suppress OOF and other auto-responses.
- $mailer->addHeader('X-Auto-Response-Suppress', 'All');
-
- // If the message has mailtags, filter out any recipients who don't want
- // to receive this type of mail.
- $mailtags = $this->getParam('mailtags');
- if ($mailtags) {
- $tag_header = array();
- foreach ($mailtags as $mailtag) {
- $tag_header[] = '<'.$mailtag.'>';
- }
- $tag_header = implode(', ', $tag_header);
- $mailer->addHeader('X-Phabricator-Mail-Tags', $tag_header);
- }
-
// Some mailers require a valid "To:" in order to deliver mail. If we
// don't have any "To:", try to fill it in with a placeholder "To:".
// If that also fails, move the "Cc:" line to "To:".
@@ -1052,6 +1035,52 @@
return $ret;
}
+ public function generateHeaders() {
+ $headers = array();
+
+ $headers[] = array('X-Phabricator-Sent-This-Message', 'Yes');
+ $headers[] = array('X-Mail-Transport-Agent', 'MetaMTA');
+
+ // Some clients respect this to suppress OOF and other auto-responses.
+ $headers[] = array('X-Auto-Response-Suppress', 'All');
+
+ // If the message has mailtags, filter out any recipients who don't want
+ // to receive this type of mail.
+ $mailtags = $this->getParam('mailtags');
+ if ($mailtags) {
+ $tag_header = array();
+ foreach ($mailtags as $mailtag) {
+ $tag_header[] = '<'.$mailtag.'>';
+ }
+ $tag_header = implode(', ', $tag_header);
+ $headers[] = array('X-Phabricator-Mail-Tags', $tag_header);
+ }
+
+ $value = $this->getParam('headers', array());
+ foreach ($value as $pair) {
+ list($header_key, $header_value) = $pair;
+
+ // NOTE: If we have \n in a header, SES rejects the email.
+ $header_value = str_replace("\n", ' ', $header_value);
+ $headers[] = array($header_key, $header_value);
+ }
+
+ $is_bulk = $this->getParam('is-bulk');
+ if ($is_bulk) {
+ $headers[] = array('Precedence', 'bulk');
+ }
+
+ return $headers;
+ }
+
+ public function getDeliveredHeaders() {
+ return $this->getParam('headers.sent');
+ }
+
+ public function getDeliveredActors() {
+ return $this->getParam('actors.sent');
+ }
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */

File Metadata

Mime Type
text/plain
Expires
Tue, May 21, 11:20 PM (3 w, 3 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6289661
Default Alt Text
D13878.id33512.diff (23 KB)

Event Timeline