2011-01-26 02:17:19 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copyright 2011 Facebook, Inc.
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
2011-02-09 18:58:48 +01:00
|
|
|
abstract protected function renderSubject();
|
|
|
|
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!');
|
|
|
|
}
|
|
|
|
|
2011-10-14 21:08:31 +02:00
|
|
|
$cc_phids = $this->getCCPHIDs();
|
|
|
|
$subject = $this->buildSubject();
|
|
|
|
$body = $this->buildBody();
|
|
|
|
$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
|
|
|
->setSubject($subject)
|
|
|
|
->setBody($body)
|
|
|
|
->setIsHTML($this->shouldMarkMailAsHTML())
|
2011-06-22 21:41:19 +02:00
|
|
|
->setParentMessageID($this->parentMessageID)
|
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
|
|
|
->addHeader('Thread-Topic', $this->getRevision()->getTitle());
|
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
|
|
|
}
|
|
|
|
|
2011-10-23 21:07:37 +02:00
|
|
|
$template->setIsBulk(true);
|
2011-05-10 01:31:26 +02:00
|
|
|
$template->setRelatedPHID($this->getRevision()->getPHID());
|
|
|
|
|
|
|
|
$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();
|
|
|
|
|
2011-11-09 00:15:44 +01:00
|
|
|
$event = new PhabricatorEvent(
|
|
|
|
PhabricatorEventType::TYPE_DIFFERENTIAL_WILLSENDMAIL,
|
|
|
|
array(
|
|
|
|
'mail' => $template,
|
|
|
|
)
|
|
|
|
);
|
2011-11-10 02:23:33 +01:00
|
|
|
PhutilEventEngine::dispatchEvent($event);
|
2011-11-09 00:15:44 +01:00
|
|
|
|
|
|
|
$template = $event->getValue('mail');
|
|
|
|
|
2011-05-10 01:31:26 +02:00
|
|
|
$mails = $reply_handler->multiplexMail(
|
|
|
|
$template,
|
|
|
|
array_select_keys($handles, $to_phids),
|
|
|
|
array_select_keys($handles, $cc_phids));
|
2011-01-26 02:17:19 +01:00
|
|
|
|
2011-05-10 01:31:26 +02:00
|
|
|
foreach ($mails as $mail) {
|
|
|
|
$mail->saveAndSend();
|
|
|
|
}
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
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 buildSubject() {
|
2011-05-17 00:54:41 +02:00
|
|
|
return trim($this->getSubjectPrefix().' '.$this->renderSubject());
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function shouldMarkMailAsHTML() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function buildBody() {
|
|
|
|
|
|
|
|
$body = $this->renderBody();
|
|
|
|
|
2011-05-10 01:31:26 +02:00
|
|
|
$reply_handler = $this->getReplyHandler();
|
|
|
|
$reply_instructions = $reply_handler->getReplyHandlerInstructions();
|
|
|
|
if ($reply_instructions) {
|
|
|
|
$body .=
|
|
|
|
"\nREPLY HANDLER ACTIONS\n".
|
|
|
|
" {$reply_instructions}\n";
|
2011-05-01 04:40:05 +02:00
|
|
|
}
|
2011-01-26 02:17:19 +01:00
|
|
|
|
|
|
|
if ($this->getHeraldTranscriptURI() && $this->isFirstMailToRecipients()) {
|
2011-04-10 17:46:17 +02:00
|
|
|
$manage_uri = PhabricatorEnv::getProductionURI(
|
|
|
|
'/herald/view/differential/');
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
$xscript_uri = $this->getHeraldTranscriptURI();
|
|
|
|
$body .= <<<EOTEXT
|
|
|
|
|
2011-04-10 17:46:17 +02:00
|
|
|
MANAGE HERALD DIFFERENTIAL RULES
|
2011-04-10 21:39:05 +02:00
|
|
|
{$manage_uri}
|
2011-01-26 02:17:19 +01:00
|
|
|
|
|
|
|
WHY DID I GET THIS EMAIL?
|
|
|
|
{$xscript_uri}
|
|
|
|
|
|
|
|
Tip: use the X-Herald-Rules header to filter Herald messages in your client.
|
|
|
|
|
|
|
|
EOTEXT;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $body;
|
|
|
|
}
|
|
|
|
|
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() {
|
2011-05-01 04:40:05 +02:00
|
|
|
if ($this->replyHandler) {
|
|
|
|
return $this->replyHandler;
|
|
|
|
}
|
|
|
|
|
2011-05-10 01:31:26 +02:00
|
|
|
$handler_class = PhabricatorEnv::getEnvConfig(
|
|
|
|
'metamta.differential.reply-handler');
|
2011-05-01 04:40:05 +02:00
|
|
|
|
2011-05-16 21:31:18 +02:00
|
|
|
$reply_handler = self::newReplyHandlerForRevision($this->getRevision());
|
2011-05-01 04:40:05 +02:00
|
|
|
|
2011-05-10 01:31:26 +02:00
|
|
|
$this->replyHandler = $reply_handler;
|
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) {
|
|
|
|
|
|
|
|
$handler_class = PhabricatorEnv::getEnvConfig(
|
|
|
|
'metamta.differential.reply-handler');
|
|
|
|
|
|
|
|
$reply_handler = newv($handler_class, array());
|
|
|
|
$reply_handler->setMailReceiver($revision);
|
|
|
|
|
|
|
|
return $reply_handler;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
protected function formatText($text) {
|
|
|
|
$text = explode("\n", $text);
|
|
|
|
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();
|
2011-04-10 17:46:17 +02:00
|
|
|
$domain = PhabricatorEnv::getEnvConfig('metamta.domain');
|
|
|
|
return "<differential-rev-{$phid}-req@{$domain}>";
|
2011-01-26 02:17:19 +01: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) {
|
|
|
|
$this->inlineComments = $inline_comments;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getInlineComments() {
|
|
|
|
return $this->inlineComments;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function renderRevisionDetailLink() {
|
|
|
|
$uri = $this->getRevisionURI();
|
|
|
|
return "REVISION DETAIL\n {$uri}";
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getRevisionURI() {
|
2011-04-10 05:50:36 +02:00
|
|
|
return PhabricatorEnv::getProductionURI('/D'.$this->getRevision()->getID());
|
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) {
|
|
|
|
$names = array();
|
|
|
|
foreach ($phids as $phid) {
|
|
|
|
$names[] = $handles[$phid]->getName();
|
|
|
|
}
|
|
|
|
return implode(', ', $names);
|
|
|
|
}
|
|
|
|
|
2011-01-26 02:17:19 +01:00
|
|
|
}
|