2011-01-26 02:17:19 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
Add email preferences to receive fewer less-important notifications
Summary:
A few similar requests have come in across several tools and use cases that I
think this does a reasonable job of resolving.
We currently send one email for each update an object receives, but these aren't
always appreciated:
- Asana does post-commit review via Differential, so the "committed" mails are
useless.
- Quora wants to make project category edits to bugs without spamming people
attached to them.
- Some users in general are very sensitive to email volumes, and this gives us
a good way to reduce the volumes without incurring the complexity of
delayed-send-batching.
The technical mechanism is basically:
- Mail may optionally have "mail tags", which indicate content in the mail
(e.g., "maniphest-priority, maniphest-cc, maniphest-comment" for a mail which
contains a priority change, a CC change, and a comment).
- If a mail has tags, remove any recipients who have opted out of all the
tags.
- Some tags can't be opted out of via the UI, so this ensures that important
email is still delivered (e.g., cc + assign + comment is always delivered
because you can't opt out of "assign" or "comment").
Test Plan:
- Disabled all mail tags in the web UI.
- Used test console to send myself mail with an opt-outable tag, it was
immediately dropped.
- Used test console to send myself mail with an opt-outable tag and a custom
tag, it was delivered.
- Made Differential updates affecting CCs with and without comments, got
appropriate delivery.
- Made Maniphest updates affecting project, priority and CCs with and without
comments, got appropriate delivery.
- Verified mail headers in all cases.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, epriestley, moskov
Maniphest Tasks: T616, T855
Differential Revision: https://secure.phabricator.com/D1635
2012-02-18 07:57:07 +01:00
|
|
|
* Copyright 2012 Facebook, Inc.
|
2011-01-26 02:17:19 +01:00
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
abstract class DifferentialMail {
|
|
|
|
|
|
|
|
protected $to = array();
|
|
|
|
protected $cc = array();
|
|
|
|
|
2011-02-09 18:58:48 +01:00
|
|
|
protected $actorHandle;
|
2011-01-26 02:17:19 +01:00
|
|
|
|
|
|
|
protected $revision;
|
2011-01-30 21:08:40 +01:00
|
|
|
protected $comment;
|
2011-01-26 02:17:19 +01:00
|
|
|
protected $changesets;
|
|
|
|
protected $inlineComments;
|
|
|
|
protected $isFirstMailAboutRevision;
|
|
|
|
protected $isFirstMailToRecipients;
|
|
|
|
protected $heraldTranscriptURI;
|
|
|
|
protected $heraldRulesHeader;
|
2011-05-01 04:40:05 +02:00
|
|
|
protected $replyHandler;
|
2011-06-22 21:41:19 +02:00
|
|
|
protected $parentMessageID;
|
2011-01-26 02:17:19 +01:00
|
|
|
|
Fix various threading issues, particularly in Gmail
Summary:
- Add an explicit multiplexing option, and enable it by default. This is necessary for Mail.app to coexist with other clients ("Re:" breaks outlook at the very least, and generally sucks in the common case), and allows users with flexible clients to enable subject variance.
- Add an option for subject line variance. Default to not varying the subject, so mail no longer says [Committed], [Closed], etc. This is so the defaults thread correctly in Gmail (not entirely sure this actually works).
- Add a preference to enable subject line variance.
- Unless all mail is multiplexed, don't enable or respect the "Re" or "vary subject" preferences. These are currently shown and respected in non-multiplex cases, which creates inconsistent results.
NOTE: @jungejason @nh @vrana This changes the default behavior (from non-multiplexing to multiplexing), and might break Facebook's integration. You should be able to keep the same behavior by setting the options appropriately, although if you can get the new defaults working they're probably better.
Test Plan:
Send mail from Maniphest, Differential and Audit. Updated preferences. Enabled/disabled multiplexing. Things seem OK?
NOTE: I haven't actually been able to repro the Gmail threading issue so I'm not totally sure what's going on there, maybe it started respecting "Re:" (or always has), but @cpiro and @20after4 both reported it independently. This fixes a bunch of bugs in any case and gives us more conservative set of defaults.
I'll see if I can buff out the Gmail story a bit but every client is basically a giant black box of mystery. :/
Reviewers: btrahan, vrana, jungejason, nh
Reviewed By: btrahan
CC: cpiro, 20after4, aran
Maniphest Tasks: T1097, T847
Differential Revision: https://secure.phabricator.com/D2206
2012-04-12 18:31:03 +02:00
|
|
|
protected function renderSubject() {
|
|
|
|
$revision = $this->getRevision();
|
|
|
|
$title = $revision->getTitle();
|
|
|
|
$id = $revision->getID();
|
|
|
|
return "D{$id}: {$title}";
|
|
|
|
}
|
|
|
|
|
2012-06-11 21:21:37 +02:00
|
|
|
abstract protected function renderVaryPrefix();
|
2011-02-09 18:58:48 +01:00
|
|
|
abstract protected function renderBody();
|
2011-01-26 02:17:19 +01:00
|
|
|
|
2011-02-09 18:58:48 +01:00
|
|
|
public function setActorHandle($actor_handle) {
|
|
|
|
$this->actorHandle = $actor_handle;
|
2011-01-26 02:17:19 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-02-09 18:58:48 +01:00
|
|
|
public function getActorHandle() {
|
|
|
|
return $this->actorHandle;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getActorName() {
|
|
|
|
$handle = $this->getActorHandle();
|
|
|
|
if ($handle) {
|
|
|
|
return $handle->getName();
|
|
|
|
}
|
|
|
|
return '???';
|
|
|
|
}
|
2011-01-26 02:17:19 +01:00
|
|
|
|
2011-06-22 21:41:19 +02:00
|
|
|
public function setParentMessageID($parent_message_id) {
|
|
|
|
$this->parentMessageID = $parent_message_id;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
public function setXHeraldRulesHeader($header) {
|
|
|
|
$this->heraldRulesHeader = $header;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function send() {
|
|
|
|
$to_phids = $this->getToPHIDs();
|
|
|
|
if (!$to_phids) {
|
|
|
|
throw new Exception('No "To:" users provided!');
|
|
|
|
}
|
|
|
|
|
Fix various threading issues, particularly in Gmail
Summary:
- Add an explicit multiplexing option, and enable it by default. This is necessary for Mail.app to coexist with other clients ("Re:" breaks outlook at the very least, and generally sucks in the common case), and allows users with flexible clients to enable subject variance.
- Add an option for subject line variance. Default to not varying the subject, so mail no longer says [Committed], [Closed], etc. This is so the defaults thread correctly in Gmail (not entirely sure this actually works).
- Add a preference to enable subject line variance.
- Unless all mail is multiplexed, don't enable or respect the "Re" or "vary subject" preferences. These are currently shown and respected in non-multiplex cases, which creates inconsistent results.
NOTE: @jungejason @nh @vrana This changes the default behavior (from non-multiplexing to multiplexing), and might break Facebook's integration. You should be able to keep the same behavior by setting the options appropriately, although if you can get the new defaults working they're probably better.
Test Plan:
Send mail from Maniphest, Differential and Audit. Updated preferences. Enabled/disabled multiplexing. Things seem OK?
NOTE: I haven't actually been able to repro the Gmail threading issue so I'm not totally sure what's going on there, maybe it started respecting "Re:" (or always has), but @cpiro and @20after4 both reported it independently. This fixes a bunch of bugs in any case and gives us more conservative set of defaults.
I'll see if I can buff out the Gmail story a bit but every client is basically a giant black box of mystery. :/
Reviewers: btrahan, vrana, jungejason, nh
Reviewed By: btrahan
CC: cpiro, 20after4, aran
Maniphest Tasks: T1097, T847
Differential Revision: https://secure.phabricator.com/D2206
2012-04-12 18:31:03 +02:00
|
|
|
$cc_phids = $this->getCCPHIDs();
|
|
|
|
$attachments = $this->buildAttachments();
|
2011-01-26 02:17:19 +01:00
|
|
|
|
2011-05-10 01:31:26 +02:00
|
|
|
$template = new PhabricatorMetaMTAMail();
|
2011-05-01 04:40:05 +02:00
|
|
|
$actor_handle = $this->getActorHandle();
|
|
|
|
$reply_handler = $this->getReplyHandler();
|
|
|
|
|
|
|
|
if ($actor_handle) {
|
2011-05-10 01:31:26 +02:00
|
|
|
$template->setFrom($actor_handle->getPHID());
|
2011-05-01 04:40:05 +02:00
|
|
|
}
|
|
|
|
|
2011-05-10 01:31:26 +02:00
|
|
|
$template
|
2011-01-26 02:17:19 +01:00
|
|
|
->setIsHTML($this->shouldMarkMailAsHTML())
|
2011-06-22 21:41:19 +02:00
|
|
|
->setParentMessageID($this->parentMessageID)
|
2012-06-12 19:49:42 +02:00
|
|
|
->addHeader('Thread-Topic', $this->getThreadTopic());
|
2011-01-26 02:17:19 +01:00
|
|
|
|
2011-11-09 00:15:44 +01:00
|
|
|
$template->setAttachments($attachments);
|
2011-10-14 21:08:31 +02:00
|
|
|
|
2011-05-10 01:31:26 +02:00
|
|
|
$template->setThreadID(
|
Fix a threading issue with Amazon SES
Summary:
Amazon SES does not allow us to set a Message-ID header, which means
that threads are incorrect in Mail.app (and presumably other applications
which respect In-Reply-To and References) because the initial email does not
have anything which attaches it to the rest of the thread. To fix this, never
rely on Message-ID if the mailer doesn't support Message-ID.
(In the Amazon SES case, Amazon generates its own Message-ID which we can't
know ahead of time).
I additionally used all the Lisk isolation from the other tests to make this
testable and wrote tests for it.
I also moved the idea of a thread ID lower in the stack and out of
DifferentialMail, which should not be responsible for implementation details.
NOTE: If you push this, it will cause a one-time break of threading for
everyone using Outlook since I've changed the seed for generating Thread-Index.
I feel like this is okay to avoid introducing more complexity here.
Test Plan:
Created and then updated a revision, messages delivered over Amazon
SES threaded correctly in Mail.app. Verified headers. Unit tests.
Reviewed By: rm
Reviewers: aran, tuomaspelkonen, jungejason, rm
Commenters: aran
CC: aran, rm, epriestley
Differential Revision: 195
2011-04-30 20:47:00 +02:00
|
|
|
$this->getThreadID(),
|
|
|
|
$this->isFirstMailAboutRevision());
|
2011-01-26 02:17:19 +01:00
|
|
|
|
|
|
|
if ($this->heraldRulesHeader) {
|
2011-05-10 01:31:26 +02:00
|
|
|
$template->addHeader('X-Herald-Rules', $this->heraldRulesHeader);
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
2012-02-27 06:22:04 +01:00
|
|
|
$revision = $this->revision;
|
|
|
|
if ($revision) {
|
|
|
|
if ($revision->getAuthorPHID()) {
|
|
|
|
$template->addHeader(
|
|
|
|
'X-Differential-Author',
|
|
|
|
'<'.$revision->getAuthorPHID().'>');
|
|
|
|
}
|
2012-06-22 03:39:35 +02:00
|
|
|
|
|
|
|
$reviewer_phids = $revision->getReviewers();
|
|
|
|
if ($reviewer_phids) {
|
|
|
|
// Add several headers to support e-mail clients which are not able to
|
|
|
|
// create rules using regular expressions or wildcards (namely Outlook).
|
|
|
|
$template->addPHIDHeaders('X-Differential-Reviewer', $reviewer_phids);
|
|
|
|
|
|
|
|
// Add it also as a list to allow matching of the first reviewer and
|
|
|
|
// also for backwards compatibility.
|
2012-02-27 06:22:04 +01:00
|
|
|
$template->addHeader(
|
|
|
|
'X-Differential-Reviewers',
|
2012-06-22 03:39:35 +02:00
|
|
|
'<'.implode('>, <', $reviewer_phids).'>');
|
2012-02-27 06:22:04 +01:00
|
|
|
}
|
2012-06-22 03:39:35 +02:00
|
|
|
|
|
|
|
if ($cc_phids) {
|
|
|
|
$template->addPHIDHeaders('X-Differential-CC', $cc_phids);
|
2012-02-27 06:22:04 +01:00
|
|
|
$template->addHeader(
|
|
|
|
'X-Differential-CCs',
|
2012-06-22 03:39:35 +02:00
|
|
|
'<'.implode('>, <', $cc_phids).'>');
|
2012-03-26 18:55:38 +02:00
|
|
|
|
|
|
|
// Determine explicit CCs (those added by humans) and put them in a
|
|
|
|
// header so users can differentiate between Herald CCs and human CCs.
|
|
|
|
|
|
|
|
$relation_subscribed = DifferentialRevision::RELATION_SUBSCRIBED;
|
|
|
|
$raw = $revision->getRawRelations($relation_subscribed);
|
|
|
|
|
|
|
|
$reason_phids = ipull($raw, 'reasonPHID');
|
|
|
|
$reason_handles = id(new PhabricatorObjectHandleData($reason_phids))
|
|
|
|
->loadHandles();
|
|
|
|
|
|
|
|
$explicit_cc = array();
|
|
|
|
foreach ($raw as $relation) {
|
|
|
|
if (!$relation['reasonPHID']) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$type = $reason_handles[$relation['reasonPHID']]->getType();
|
|
|
|
if ($type == PhabricatorPHIDConstants::PHID_TYPE_USER) {
|
|
|
|
$explicit_cc[] = $relation['objectPHID'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($explicit_cc) {
|
2012-06-22 03:39:35 +02:00
|
|
|
$template->addPHIDHeaders('X-Differential-Explicit-CC', $explicit_cc);
|
2012-03-26 18:55:38 +02:00
|
|
|
$template->addHeader(
|
|
|
|
'X-Differential-Explicit-CCs',
|
|
|
|
'<'.implode('>, <', $explicit_cc).'>');
|
|
|
|
}
|
2012-02-27 06:22:04 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-23 21:07:37 +02:00
|
|
|
$template->setIsBulk(true);
|
2011-05-10 01:31:26 +02:00
|
|
|
$template->setRelatedPHID($this->getRevision()->getPHID());
|
|
|
|
|
Add email preferences to receive fewer less-important notifications
Summary:
A few similar requests have come in across several tools and use cases that I
think this does a reasonable job of resolving.
We currently send one email for each update an object receives, but these aren't
always appreciated:
- Asana does post-commit review via Differential, so the "committed" mails are
useless.
- Quora wants to make project category edits to bugs without spamming people
attached to them.
- Some users in general are very sensitive to email volumes, and this gives us
a good way to reduce the volumes without incurring the complexity of
delayed-send-batching.
The technical mechanism is basically:
- Mail may optionally have "mail tags", which indicate content in the mail
(e.g., "maniphest-priority, maniphest-cc, maniphest-comment" for a mail which
contains a priority change, a CC change, and a comment).
- If a mail has tags, remove any recipients who have opted out of all the
tags.
- Some tags can't be opted out of via the UI, so this ensures that important
email is still delivered (e.g., cc + assign + comment is always delivered
because you can't opt out of "assign" or "comment").
Test Plan:
- Disabled all mail tags in the web UI.
- Used test console to send myself mail with an opt-outable tag, it was
immediately dropped.
- Used test console to send myself mail with an opt-outable tag and a custom
tag, it was delivered.
- Made Differential updates affecting CCs with and without comments, got
appropriate delivery.
- Made Maniphest updates affecting project, priority and CCs with and without
comments, got appropriate delivery.
- Verified mail headers in all cases.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, epriestley, moskov
Maniphest Tasks: T616, T855
Differential Revision: https://secure.phabricator.com/D1635
2012-02-18 07:57:07 +01:00
|
|
|
$mailtags = $this->getMailTags();
|
|
|
|
if ($mailtags) {
|
|
|
|
$template->setMailTags($mailtags);
|
|
|
|
}
|
|
|
|
|
2011-05-10 01:31:26 +02:00
|
|
|
$phids = array();
|
|
|
|
foreach ($to_phids as $phid) {
|
|
|
|
$phids[$phid] = true;
|
|
|
|
}
|
|
|
|
foreach ($cc_phids as $phid) {
|
|
|
|
$phids[$phid] = true;
|
|
|
|
}
|
|
|
|
$phids = array_keys($phids);
|
|
|
|
|
|
|
|
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
|
2012-06-16 08:21:25 +02:00
|
|
|
$objects = id(new PhabricatorObjectHandleData($phids))->loadObjects();
|
2011-05-10 01:31:26 +02:00
|
|
|
|
2012-06-16 08:21:25 +02:00
|
|
|
$to_handles = array_select_keys($handles, $to_phids);
|
|
|
|
$cc_handles = array_select_keys($handles, $cc_phids);
|
2011-11-09 00:15:44 +01:00
|
|
|
|
2012-06-16 08:21:25 +02:00
|
|
|
$this->prepareBody();
|
2011-11-09 00:15:44 +01:00
|
|
|
|
2012-06-16 08:21:25 +02:00
|
|
|
$mails = $reply_handler->multiplexMail($template, $to_handles, $cc_handles);
|
2011-01-26 02:17:19 +01:00
|
|
|
|
2012-06-16 08:21:25 +02:00
|
|
|
$original_translator = PhutilTranslator::getInstance();
|
|
|
|
if (!PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
|
|
|
|
$translation = PhabricatorEnv::newObjectFromConfig(
|
|
|
|
'translation.provider');
|
|
|
|
$translator = id(new PhutilTranslator())
|
|
|
|
->setLanguage($translation->getLanguage())
|
|
|
|
->addTranslations($translation->getTranslations());
|
2011-05-10 01:31:26 +02:00
|
|
|
}
|
2012-06-16 08:21:25 +02:00
|
|
|
|
|
|
|
try {
|
|
|
|
foreach ($mails as $mail) {
|
|
|
|
if (PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
|
|
|
|
$translation = newv($mail->getTranslation($objects), array());
|
|
|
|
$translator = id(new PhutilTranslator())
|
|
|
|
->setLanguage($translation->getLanguage())
|
|
|
|
->addTranslations($translation->getTranslations());
|
|
|
|
PhutilTranslator::setInstance($translator);
|
|
|
|
}
|
|
|
|
|
|
|
|
$body =
|
|
|
|
$this->buildBody()."\n".
|
|
|
|
$reply_handler->getRecipientsSummary($to_handles, $cc_handles);
|
|
|
|
|
|
|
|
$mail
|
|
|
|
->setSubject($this->renderSubject())
|
|
|
|
->setSubjectPrefix($this->getSubjectPrefix())
|
|
|
|
->setVarySubjectPrefix($this->renderVaryPrefix())
|
|
|
|
->setBody($body);
|
|
|
|
|
|
|
|
$event = new PhabricatorEvent(
|
|
|
|
PhabricatorEventType::TYPE_DIFFERENTIAL_WILLSENDMAIL,
|
|
|
|
array(
|
|
|
|
'mail' => $mail,
|
|
|
|
)
|
|
|
|
);
|
|
|
|
PhutilEventEngine::dispatchEvent($event);
|
|
|
|
$mail = $event->getValue('mail');
|
|
|
|
|
|
|
|
$mail->saveAndSend();
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (Exception $ex) {
|
|
|
|
PhutilTranslator::setInstance($original_translator);
|
|
|
|
throw $ex;
|
|
|
|
}
|
|
|
|
|
|
|
|
PhutilTranslator::setInstance($original_translator);
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
Add email preferences to receive fewer less-important notifications
Summary:
A few similar requests have come in across several tools and use cases that I
think this does a reasonable job of resolving.
We currently send one email for each update an object receives, but these aren't
always appreciated:
- Asana does post-commit review via Differential, so the "committed" mails are
useless.
- Quora wants to make project category edits to bugs without spamming people
attached to them.
- Some users in general are very sensitive to email volumes, and this gives us
a good way to reduce the volumes without incurring the complexity of
delayed-send-batching.
The technical mechanism is basically:
- Mail may optionally have "mail tags", which indicate content in the mail
(e.g., "maniphest-priority, maniphest-cc, maniphest-comment" for a mail which
contains a priority change, a CC change, and a comment).
- If a mail has tags, remove any recipients who have opted out of all the
tags.
- Some tags can't be opted out of via the UI, so this ensures that important
email is still delivered (e.g., cc + assign + comment is always delivered
because you can't opt out of "assign" or "comment").
Test Plan:
- Disabled all mail tags in the web UI.
- Used test console to send myself mail with an opt-outable tag, it was
immediately dropped.
- Used test console to send myself mail with an opt-outable tag and a custom
tag, it was delivered.
- Made Differential updates affecting CCs with and without comments, got
appropriate delivery.
- Made Maniphest updates affecting project, priority and CCs with and without
comments, got appropriate delivery.
- Verified mail headers in all cases.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, epriestley, moskov
Maniphest Tasks: T616, T855
Differential Revision: https://secure.phabricator.com/D1635
2012-02-18 07:57:07 +01:00
|
|
|
protected function getMailTags() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2011-05-17 00:54:41 +02:00
|
|
|
protected function getSubjectPrefix() {
|
|
|
|
return PhabricatorEnv::getEnvConfig('metamta.differential.subject-prefix');
|
|
|
|
}
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
protected function shouldMarkMailAsHTML() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-06-16 08:21:25 +02:00
|
|
|
/**
|
|
|
|
* @{method:buildBody} is called once for each e-mail recipient to allow
|
|
|
|
* translating text to his language. This method can be used to load data that
|
|
|
|
* don't need translation and use them later in @{method:buildBody}.
|
|
|
|
*
|
|
|
|
* @param
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
protected function prepareBody() {
|
|
|
|
}
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
protected function buildBody() {
|
2012-07-17 04:01:43 +02:00
|
|
|
$main_body = $this->renderBody();
|
2011-01-26 02:17:19 +01:00
|
|
|
|
2012-07-17 04:01:43 +02:00
|
|
|
$body = new PhabricatorMetaMTAMailBody();
|
|
|
|
$body->addRawSection($main_body);
|
2011-01-26 02:17:19 +01:00
|
|
|
|
2011-05-10 01:31:26 +02:00
|
|
|
$reply_handler = $this->getReplyHandler();
|
2012-07-17 04:01:43 +02:00
|
|
|
$body->addReplySection($reply_handler->getReplyHandlerInstructions());
|
2011-01-26 02:17:19 +01:00
|
|
|
|
|
|
|
if ($this->getHeraldTranscriptURI() && $this->isFirstMailToRecipients()) {
|
2012-07-17 04:01:43 +02:00
|
|
|
$manage_uri = '/herald/view/differential/';
|
2011-01-26 02:17:19 +01:00
|
|
|
$xscript_uri = $this->getHeraldTranscriptURI();
|
2012-07-17 04:01:43 +02:00
|
|
|
$body->addHeraldSection($manage_uri, $xscript_uri);
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
2012-07-17 04:01:43 +02:00
|
|
|
return $body->render();
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
2011-10-14 21:08:31 +02:00
|
|
|
/**
|
|
|
|
* You can override this method in a subclass and return array of attachments
|
2011-11-09 00:15:44 +01:00
|
|
|
* to be sent with the email. Each attachment is an instance of
|
|
|
|
* PhabricatorMetaMTAAttachment.
|
2011-10-14 21:08:31 +02:00
|
|
|
*/
|
|
|
|
protected function buildAttachments() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2011-05-16 21:31:18 +02:00
|
|
|
public function getReplyHandler() {
|
2012-03-15 20:15:23 +01:00
|
|
|
if (!$this->replyHandler) {
|
|
|
|
$this->replyHandler =
|
|
|
|
self::newReplyHandlerForRevision($this->getRevision());
|
2011-05-01 04:40:05 +02:00
|
|
|
}
|
|
|
|
|
2011-05-10 01:31:26 +02:00
|
|
|
return $this->replyHandler;
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
2011-05-16 21:31:18 +02:00
|
|
|
public static function newReplyHandlerForRevision(
|
|
|
|
DifferentialRevision $revision) {
|
|
|
|
|
2012-03-15 20:15:23 +01:00
|
|
|
$reply_handler = PhabricatorEnv::newObjectFromConfig(
|
2011-05-16 21:31:18 +02:00
|
|
|
'metamta.differential.reply-handler');
|
|
|
|
$reply_handler->setMailReceiver($revision);
|
|
|
|
|
|
|
|
return $reply_handler;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
protected function formatText($text) {
|
2012-06-13 00:14:45 +02:00
|
|
|
$text = explode("\n", rtrim($text));
|
2011-01-26 02:17:19 +01:00
|
|
|
foreach ($text as &$line) {
|
|
|
|
$line = rtrim(' '.$line);
|
|
|
|
}
|
|
|
|
unset($line);
|
|
|
|
return implode("\n", $text);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setToPHIDs(array $to) {
|
|
|
|
$this->to = $this->filterContactPHIDs($to);
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setCCPHIDs(array $cc) {
|
|
|
|
$this->cc = $this->filterContactPHIDs($cc);
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function filterContactPHIDs(array $phids) {
|
|
|
|
return $phids;
|
|
|
|
|
|
|
|
// TODO: actually do this?
|
|
|
|
|
|
|
|
// Differential revisions use Subscriptions for CCs, so any arbitrary
|
|
|
|
// PHID can end up CC'd to them. Only try to actually send email PHIDs
|
|
|
|
// which have ToolsHandle types that are marked emailable. If we don't
|
|
|
|
// filter here, sending the email will fail.
|
|
|
|
/*
|
|
|
|
$handles = array();
|
|
|
|
prep(new ToolsHandleData($phids, $handles));
|
|
|
|
foreach ($handles as $phid => $handle) {
|
|
|
|
if (!$handle->isEmailable()) {
|
|
|
|
unset($handles[$phid]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return array_keys($handles);
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getToPHIDs() {
|
|
|
|
return $this->to;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getCCPHIDs() {
|
|
|
|
return $this->cc;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setRevision($revision) {
|
|
|
|
$this->revision = $revision;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getRevision() {
|
|
|
|
return $this->revision;
|
|
|
|
}
|
|
|
|
|
Fix a threading issue with Amazon SES
Summary:
Amazon SES does not allow us to set a Message-ID header, which means
that threads are incorrect in Mail.app (and presumably other applications
which respect In-Reply-To and References) because the initial email does not
have anything which attaches it to the rest of the thread. To fix this, never
rely on Message-ID if the mailer doesn't support Message-ID.
(In the Amazon SES case, Amazon generates its own Message-ID which we can't
know ahead of time).
I additionally used all the Lisk isolation from the other tests to make this
testable and wrote tests for it.
I also moved the idea of a thread ID lower in the stack and out of
DifferentialMail, which should not be responsible for implementation details.
NOTE: If you push this, it will cause a one-time break of threading for
everyone using Outlook since I've changed the seed for generating Thread-Index.
I feel like this is okay to avoid introducing more complexity here.
Test Plan:
Created and then updated a revision, messages delivered over Amazon
SES threaded correctly in Mail.app. Verified headers. Unit tests.
Reviewed By: rm
Reviewers: aran, tuomaspelkonen, jungejason, rm
Commenters: aran
CC: aran, rm, epriestley
Differential Revision: 195
2011-04-30 20:47:00 +02:00
|
|
|
protected function getThreadID() {
|
2011-01-26 02:17:19 +01:00
|
|
|
$phid = $this->getRevision()->getPHID();
|
Fix various threading issues, particularly in Gmail
Summary:
- Add an explicit multiplexing option, and enable it by default. This is necessary for Mail.app to coexist with other clients ("Re:" breaks outlook at the very least, and generally sucks in the common case), and allows users with flexible clients to enable subject variance.
- Add an option for subject line variance. Default to not varying the subject, so mail no longer says [Committed], [Closed], etc. This is so the defaults thread correctly in Gmail (not entirely sure this actually works).
- Add a preference to enable subject line variance.
- Unless all mail is multiplexed, don't enable or respect the "Re" or "vary subject" preferences. These are currently shown and respected in non-multiplex cases, which creates inconsistent results.
NOTE: @jungejason @nh @vrana This changes the default behavior (from non-multiplexing to multiplexing), and might break Facebook's integration. You should be able to keep the same behavior by setting the options appropriately, although if you can get the new defaults working they're probably better.
Test Plan:
Send mail from Maniphest, Differential and Audit. Updated preferences. Enabled/disabled multiplexing. Things seem OK?
NOTE: I haven't actually been able to repro the Gmail threading issue so I'm not totally sure what's going on there, maybe it started respecting "Re:" (or always has), but @cpiro and @20after4 both reported it independently. This fixes a bunch of bugs in any case and gives us more conservative set of defaults.
I'll see if I can buff out the Gmail story a bit but every client is basically a giant black box of mystery. :/
Reviewers: btrahan, vrana, jungejason, nh
Reviewed By: btrahan
CC: cpiro, 20after4, aran
Maniphest Tasks: T1097, T847
Differential Revision: https://secure.phabricator.com/D2206
2012-04-12 18:31:03 +02:00
|
|
|
return "differential-rev-{$phid}-req";
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
2012-06-12 19:49:42 +02:00
|
|
|
protected function getThreadTopic() {
|
2012-06-14 05:52:10 +02:00
|
|
|
$id = $this->getRevision()->getID();
|
|
|
|
$title = $this->getRevision()->getOriginalTitle();
|
|
|
|
return "D{$id}: {$title}";
|
2012-06-12 19:49:42 +02:00
|
|
|
}
|
|
|
|
|
2011-01-30 21:08:40 +01:00
|
|
|
public function setComment($comment) {
|
|
|
|
$this->comment = $comment;
|
2011-01-26 02:17:19 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-01-30 21:08:40 +01:00
|
|
|
public function getComment() {
|
|
|
|
return $this->comment;
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function setChangesets($changesets) {
|
|
|
|
$this->changesets = $changesets;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getChangesets() {
|
|
|
|
return $this->changesets;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setInlineComments(array $inline_comments) {
|
2012-04-04 22:13:08 +02:00
|
|
|
assert_instances_of($inline_comments, 'PhabricatorInlineCommentInterface');
|
2011-01-26 02:17:19 +01:00
|
|
|
$this->inlineComments = $inline_comments;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getInlineComments() {
|
|
|
|
return $this->inlineComments;
|
|
|
|
}
|
|
|
|
|
2012-06-06 19:52:17 +02:00
|
|
|
protected function renderAuxFields($phase) {
|
|
|
|
$selector = DifferentialFieldSelector::newSelector();
|
|
|
|
$aux_fields = $selector->sortFieldsForMail(
|
|
|
|
$selector->getFieldSpecifications());
|
|
|
|
|
|
|
|
$body = array();
|
|
|
|
foreach ($aux_fields as $field) {
|
|
|
|
$field->setRevision($this->getRevision());
|
2012-06-16 08:21:25 +02:00
|
|
|
// TODO: Introduce and use getRequiredHandlePHIDsForMail() and load all
|
|
|
|
// handles in prepareBody().
|
2012-06-06 19:52:17 +02:00
|
|
|
$text = $field->renderValueForMail($phase);
|
|
|
|
if ($text !== null) {
|
|
|
|
$body[] = $text;
|
|
|
|
$body[] = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return implode("\n", $body);
|
|
|
|
}
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
public function setIsFirstMailToRecipients($first) {
|
|
|
|
$this->isFirstMailToRecipients = $first;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function isFirstMailToRecipients() {
|
|
|
|
return $this->isFirstMailToRecipients;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setIsFirstMailAboutRevision($first) {
|
|
|
|
$this->isFirstMailAboutRevision = $first;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function isFirstMailAboutRevision() {
|
|
|
|
return $this->isFirstMailAboutRevision;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setHeraldTranscriptURI($herald_transcript_uri) {
|
|
|
|
$this->heraldTranscriptURI = $herald_transcript_uri;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getHeraldTranscriptURI() {
|
|
|
|
return $this->heraldTranscriptURI;
|
|
|
|
}
|
|
|
|
|
2011-07-01 03:10:26 +02:00
|
|
|
protected function renderHandleList(array $handles, array $phids) {
|
2012-04-04 22:13:08 +02:00
|
|
|
assert_instances_of($handles, 'PhabricatorObjectHandle');
|
2011-07-01 03:10:26 +02:00
|
|
|
$names = array();
|
|
|
|
foreach ($phids as $phid) {
|
|
|
|
$names[] = $handles[$phid]->getName();
|
|
|
|
}
|
|
|
|
return implode(', ', $names);
|
|
|
|
}
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|