diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2021,6 +2021,7 @@ 'PhabricatorMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php', 'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php', 'PhabricatorMailSetupCheck' => 'applications/config/check/PhabricatorMailSetupCheck.php', + 'PhabricatorMailTarget' => 'applications/metamta/replyhandler/PhabricatorMailTarget.php', 'PhabricatorMailgunConfigOptions' => 'applications/config/option/PhabricatorMailgunConfigOptions.php', 'PhabricatorMainMenuSearchView' => 'view/page/menu/PhabricatorMainMenuSearchView.php', 'PhabricatorMainMenuView' => 'view/page/menu/PhabricatorMainMenuView.php', @@ -5429,6 +5430,7 @@ 'PhabricatorMailManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorMailReceiverTestCase' => 'PhabricatorTestCase', 'PhabricatorMailSetupCheck' => 'PhabricatorSetupCheck', + 'PhabricatorMailTarget' => 'Phobject', 'PhabricatorMailgunConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorMainMenuSearchView' => 'AphrontView', 'PhabricatorMainMenuView' => 'AphrontView', diff --git a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php --- a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php +++ b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php @@ -54,6 +54,8 @@ of each approach are: - One mail to everyone: + - This violates policy controls. The body of the mail is generated without + respect for object policies. - Recipients can see To/Cc at a glance. - If you use mailing lists, you won't get duplicate mail if you're a normal recipient and also Cc'd on a mailing list. @@ -65,6 +67,7 @@ - Not supported with a private reply-to address. - Mails are sent in the server default translation. - One mail to each user: + - Policy controls work correctly and are enforced per-user. - Recipients need to look in the mail body to see To/Cc. - If you use mailing lists, recipients may sometimes get duplicate mail. @@ -74,8 +77,6 @@ - Required if private reply-to addresses are configured. - Mails are sent in the language of user preference. -In the code, splitting one outbound email into one-per-recipient is sometimes -referred to as "multiplexing". EODOC )); @@ -222,6 +223,7 @@ 'metamta.one-mail-per-recipient', 'bool', true) + ->setLocked(true) ->setBoolOptions( array( pht('Send Mail To Each Recipient'), diff --git a/src/applications/conpherence/mail/ConpherenceReplyHandler.php b/src/applications/conpherence/mail/ConpherenceReplyHandler.php --- a/src/applications/conpherence/mail/ConpherenceReplyHandler.php +++ b/src/applications/conpherence/mail/ConpherenceReplyHandler.php @@ -21,9 +21,8 @@ } } - public function getPrivateReplyHandlerEmailAddress( - PhabricatorObjectHandle $handle) { - return $this->getDefaultPrivateReplyHandlerEmailAddress($handle, 'Z'); + public function getPrivateReplyHandlerEmailAddress(PhabricatorUser $user) { + return $this->getDefaultPrivateReplyHandlerEmailAddress($user, 'Z'); } public function getPublicReplyHandlerEmailAddress() { diff --git a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php --- a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php +++ b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php @@ -47,7 +47,7 @@ abstract public function validateMailReceiver($mail_receiver); abstract public function getPrivateReplyHandlerEmailAddress( - PhabricatorObjectHandle $handle); + PhabricatorUser $user); public function getReplyHandlerDomain() { return PhabricatorEnv::getEnvConfig('metamta.reply-handler-domain'); @@ -117,151 +117,6 @@ return null; } - final public function getRecipientsSummary( - array $to_handles, - array $cc_handles) { - assert_instances_of($to_handles, 'PhabricatorObjectHandle'); - assert_instances_of($cc_handles, 'PhabricatorObjectHandle'); - - $body = ''; - - if (PhabricatorEnv::getEnvConfig('metamta.recipients.show-hints')) { - if ($to_handles) { - $body .= "To: ".implode(', ', mpull($to_handles, 'getName'))."\n"; - } - if ($cc_handles) { - $body .= "Cc: ".implode(', ', mpull($cc_handles, 'getName'))."\n"; - } - } - - return $body; - } - - final public function getRecipientsSummaryHTML( - array $to_handles, - array $cc_handles) { - assert_instances_of($to_handles, 'PhabricatorObjectHandle'); - assert_instances_of($cc_handles, 'PhabricatorObjectHandle'); - - if (PhabricatorEnv::getEnvConfig('metamta.recipients.show-hints')) { - $body = array(); - if ($to_handles) { - $body[] = phutil_tag('strong', array(), 'To: '); - $body[] = phutil_implode_html(', ', mpull($to_handles, 'getName')); - $body[] = phutil_tag('br'); - } - if ($cc_handles) { - $body[] = phutil_tag('strong', array(), 'Cc: '); - $body[] = phutil_implode_html(', ', mpull($cc_handles, 'getName')); - $body[] = phutil_tag('br'); - } - return phutil_tag('div', array(), $body); - } else { - return ''; - } - - } - - final public function multiplexMail( - PhabricatorMetaMTAMail $mail_template, - array $to_handles, - array $cc_handles) { - assert_instances_of($to_handles, 'PhabricatorObjectHandle'); - assert_instances_of($cc_handles, 'PhabricatorObjectHandle'); - - $result = array(); - - // If MetaMTA is configured to always multiplex, skip the single-email - // case. - if (!PhabricatorMetaMTAMail::shouldMultiplexAllMail()) { - // If private replies are not supported, simply send one email to all - // recipients and CCs. This covers cases where we have no reply handler, - // or we have a public reply handler. - if (!$this->supportsPrivateReplies()) { - $mail = clone $mail_template; - $mail->addTos(mpull($to_handles, 'getPHID')); - $mail->addCCs(mpull($cc_handles, 'getPHID')); - - if ($this->supportsPublicReplies()) { - $reply_to = $this->getPublicReplyHandlerEmailAddress(); - $mail->setReplyTo($reply_to); - } - - $result[] = $mail; - - return $result; - } - } - - // TODO: This is pretty messy. We should really be doing all of this - // multiplexing in the task queue, but that requires significant rewriting - // in the general case. ApplicationTransactions can do it fairly easily, - // but other mail sites currently can not, so we need to support this - // junky version until they catch up and we can swap things over. - - $to_handles = $this->expandRecipientHandles($to_handles); - $cc_handles = $this->expandRecipientHandles($cc_handles); - - $tos = mpull($to_handles, null, 'getPHID'); - $ccs = mpull($cc_handles, null, 'getPHID'); - - // Merge all the recipients together. TODO: We could keep the CCs as real - // CCs and send to a "noreply@domain.com" type address, but keep it simple - // for now. - $recipients = $tos + $ccs; - - // When multiplexing mail, explicitly include To/Cc information in the - // message body and headers. - - $mail_template = clone $mail_template; - - $mail_template->addPHIDHeaders('X-Phabricator-To', array_keys($tos)); - $mail_template->addPHIDHeaders('X-Phabricator-Cc', array_keys($ccs)); - - $body = $mail_template->getBody(); - $body .= "\n"; - $body .= $this->getRecipientsSummary($to_handles, $cc_handles); - - $html_body = $mail_template->getHTMLBody(); - if (strlen($html_body)) { - $html_body .= hsprintf('%s', - $this->getRecipientsSummaryHTML($to_handles, $cc_handles)); - } - - foreach ($recipients as $phid => $recipient) { - - $mail = clone $mail_template; - if (isset($to_handles[$phid])) { - $mail->addTos(array($phid)); - } else if (isset($cc_handles[$phid])) { - $mail->addCCs(array($phid)); - } else { - // not good - they should be a to or a cc - continue; - } - - $mail->setBody($body); - $mail->setHTMLBody($html_body); - - $reply_to = null; - if (!$reply_to && $this->supportsPrivateReplies()) { - $reply_to = $this->getPrivateReplyHandlerEmailAddress($recipient); - } - - if (!$reply_to && $this->supportsPublicReplies()) { - $reply_to = $this->getPublicReplyHandlerEmailAddress(); - } - - if ($reply_to) { - $mail->setReplyTo($reply_to); - } - - $result[] = $mail; - } - - return $result; - } - protected function getDefaultPublicReplyHandlerEmailAddress($prefix) { $receiver = $this->getMailReceiver(); @@ -288,31 +143,15 @@ } protected function getDefaultPrivateReplyHandlerEmailAddress( - PhabricatorObjectHandle $handle, + PhabricatorUser $user, $prefix) { - if ($handle->getType() != PhabricatorPeopleUserPHIDType::TYPECONST) { - // You must be a real user to get a private reply handler address. - return null; - } - - $user = id(new PhabricatorPeopleQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs(array($handle->getPHID())) - ->executeOne(); - - if (!$user) { - // This may happen if a user was subscribed to something, and was then - // deleted. - return null; - } - $receiver = $this->getMailReceiver(); $receiver_id = $receiver->getID(); $user_id = $user->getID(); $hash = PhabricatorObjectMailReceiver::computeMailHash( $receiver->getMailKey(), - $handle->getPHID()); + $user->getPHID()); $domain = $this->getReplyHandlerDomain(); $address = "{$prefix}{$receiver_id}+{$user_id}+{$hash}@{$domain}"; @@ -368,21 +207,188 @@ return rtrim($output); } - private function expandRecipientHandles(array $handles) { - if (!$handles) { + + /** + * Produce a list of mail targets for a given to/cc list. + * + * Each target should be sent a separate email, and contains the information + * required to generate it with appropriate permissions and configuration. + * + * @param list List of "To" PHIDs. + * @param list List of "CC" PHIDs. + * @return list List of targets. + */ + final public function getMailTargets(array $raw_to, array $raw_cc) { + list($to, $cc) = $this->expandRecipientPHIDs($raw_to, $raw_cc); + list($to, $cc) = $this->loadRecipientUsers($to, $cc); + list($to, $cc) = $this->filterRecipientUsers($to, $cc); + + if (!$to && !$cc) { return array(); } - $phids = mpull($handles, 'getPHID'); - $results = id(new PhabricatorMetaMTAMemberQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($phids) - ->executeExpansion(); + $template = id(new PhabricatorMailTarget()) + ->setRawToPHIDs($raw_to) + ->setRawCCPHIDs($raw_cc); - return id(new PhabricatorHandleQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($results) - ->execute(); + // Set the public reply address as the default, if one exists. We + // might replace this with a private address later. + if ($this->supportsPublicReplies()) { + $reply_to = $this->getPublicReplyHandlerEmailAddress(); + if ($reply_to) { + $template->setReplyTo($reply_to); + } + } + + $supports_private_replies = $this->supportsPrivateReplies(); + $mail_all = !PhabricatorEnv::getEnvConfig('metamta.one-mail-per-recipient'); + $targets = array(); + if ($mail_all) { + $target = id(clone $template) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setToMap($to) + ->setCCMap($cc); + + $targets[] = $target; + } else { + $map = $to + $cc; + + foreach ($map as $phid => $user) { + $target = id(clone $template) + ->setViewer($user) + ->setToMap(array($phid => $user)) + ->setCCMap(array()); + + if ($supports_private_replies) { + $reply_to = $this->getPrivateReplyHandlerEmailAddress($user); + if ($reply_to) { + $target->setReplyTo($reply_to); + } + } + + $targets[] = $target; + } + } + + return $targets; + } + + + /** + * Expand lists of recipient PHIDs. + * + * This takes any compound recipients (like projects) and looks up all their + * members. + * + * @param list List of To PHIDs. + * @param list List of CC PHIDs. + * @return pair, list> Expanded PHID lists. + */ + private function expandRecipientPHIDs(array $to, array $cc) { + $to_result = array(); + $cc_result = array(); + + $all_phids = array_merge($to, $cc); + if ($all_phids) { + $map = id(new PhabricatorMetaMTAMemberQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($all_phids) + ->execute(); + foreach ($to as $phid) { + foreach ($map[$phid] as $expanded) { + $to_result[$expanded] = $expanded; + } + } + foreach ($cc as $phid) { + foreach ($map[$phid] as $expanded) { + $cc_result[$expanded] = $expanded; + } + } + } + + // Remove recipients from "CC" if they're also present in "To". + $cc_result = array_diff_key($cc_result, $to_result); + + return array(array_values($to_result), array_values($cc_result)); + } + + + /** + * Load @{class:PhabricatorUser} objects for each recipient. + * + * Invalid recipients are dropped from the results. + * + * @param list List of To PHIDs. + * @param list List of CC PHIDs. + * @return pair Maps from PHIDs to users. + */ + private function loadRecipientUsers(array $to, array $cc) { + $to_result = array(); + $cc_result = array(); + + $all_phids = array_merge($to, $cc); + if ($all_phids) { + $users = id(new PhabricatorPeopleQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($all_phids) + ->execute(); + $users = mpull($users, null, 'getPHID'); + + foreach ($to as $phid) { + if (isset($users[$phid])) { + $to_result[$phid] = $users[$phid]; + } + } + foreach ($cc as $phid) { + if (isset($users[$phid])) { + $cc_result[$phid] = $users[$phid]; + } + } + } + + return array($to_result, $cc_result); + } + + + /** + * Remove recipients who do not have permission to view the mail receiver. + * + * @param map Map of "To" users. + * @param map Map of "CC" users. + * @return pair Filtered user maps. + */ + private function filterRecipientUsers(array $to, array $cc) { + $to_result = array(); + $cc_result = array(); + + $all_users = $to + $cc; + if ($all_users) { + $can_see = array(); + $object = $this->getMailReceiver(); + foreach ($all_users as $phid => $user) { + $visible = PhabricatorPolicyFilter::hasCapability( + $user, + $object, + PhabricatorPolicyCapability::CAN_VIEW); + if ($visible) { + $can_see[$phid] = true; + } + } + + foreach ($to as $phid => $user) { + if (!empty($can_see[$phid])) { + $to_result[$phid] = $all_users[$phid]; + } + } + + foreach ($cc as $phid => $user) { + if (!empty($can_see[$phid])) { + $cc_result[$phid] = $all_users[$phid]; + } + } + } + + return array($to_result, $cc_result); } } diff --git a/src/applications/metamta/replyhandler/PhabricatorMailTarget.php b/src/applications/metamta/replyhandler/PhabricatorMailTarget.php new file mode 100644 --- /dev/null +++ b/src/applications/metamta/replyhandler/PhabricatorMailTarget.php @@ -0,0 +1,146 @@ +rawToPHIDs = $to_phids; + return $this; + } + + public function setRawCCPHIDs(array $cc_phids) { + $this->rawCCPHIDs = $cc_phids; + return $this; + } + + public function setCCMap(array $cc_map) { + $this->ccMap = $cc_map; + return $this; + } + + public function getCCMap() { + return $this->ccMap; + } + + public function setToMap(array $to_map) { + $this->toMap = $to_map; + return $this; + } + + public function getToMap() { + return $this->toMap; + } + + public function setReplyTo($reply_to) { + $this->replyTo = $reply_to; + return $this; + } + + public function getReplyTo() { + return $this->replyTo; + } + + public function setViewer($viewer) { + $this->viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function sendMail(PhabricatorMetaMTAMail $mail) { + $viewer = $this->getViewer(); + + $mail->addPHIDHeaders('X-Phabricator-To', $this->rawToPHIDs); + $mail->addPHIDHeaders('X-Phabricator-Cc', $this->rawCCPHIDs); + + $to_handles = $viewer->loadHandles($this->rawToPHIDs); + $cc_handles = $viewer->loadHandles($this->rawCCPHIDs); + + $body = $mail->getBody(); + $body .= "\n"; + $body .= $this->getRecipientsSummary($to_handles, $cc_handles); + $mail->setBody($body); + + $html_body = $mail->getHTMLBody(); + if (strlen($html_body)) { + $html_body .= hsprintf( + '%s', + $this->getRecipientsSummaryHTML($to_handles, $cc_handles)); + } + $mail->setHTMLBody($html_body); + + $reply_to = $this->getReplyTo(); + if ($reply_to) { + $mail->setReplyTo($reply_to); + } + + $to = array_keys($this->getToMap()); + if ($to) { + $mail->addTos($to); + } + + $cc = array_keys($this->getCCMap()); + if ($cc) { + $mail->addCCs($cc); + } + + return $mail->save(); + } + + private function getRecipientsSummary( + PhabricatorHandleList $to_handles, + PhabricatorHandleList $cc_handles) { + + if (!PhabricatorEnv::getEnvConfig('metamta.recipients.show-hints')) { + return ''; + } + + $to_handles = iterator_to_array($to_handles); + $cc_handles = iterator_to_array($cc_handles); + + $body = ''; + if ($to_handles) { + $body .= "To: ".implode(', ', mpull($to_handles, 'getName'))."\n"; + } + if ($cc_handles) { + $body .= "Cc: ".implode(', ', mpull($cc_handles, 'getName'))."\n"; + } + + return $body; + } + + private function getRecipientsSummaryHTML( + PhabricatorHandleList $to_handles, + PhabricatorHandleList $cc_handles) { + + if (!PhabricatorEnv::getEnvConfig('metamta.recipients.show-hints')) { + return ''; + } + + $to_handles = iterator_to_array($to_handles); + $cc_handles = iterator_to_array($cc_handles); + + $body = array(); + if ($to_handles) { + $body[] = phutil_tag('strong', array(), 'To: '); + $body[] = phutil_implode_html(', ', mpull($to_handles, 'getName')); + $body[] = phutil_tag('br'); + } + if ($cc_handles) { + $body[] = phutil_tag('strong', array(), 'Cc: '); + $body[] = phutil_implode_html(', ', mpull($cc_handles, 'getName')); + $body[] = phutil_tag('br'); + } + return phutil_tag('div', array(), $body); + } + + +} diff --git a/src/applications/owners/mail/OwnersPackageReplyHandler.php b/src/applications/owners/mail/OwnersPackageReplyHandler.php --- a/src/applications/owners/mail/OwnersPackageReplyHandler.php +++ b/src/applications/owners/mail/OwnersPackageReplyHandler.php @@ -11,7 +11,7 @@ } public function getPrivateReplyHandlerEmailAddress( - PhabricatorObjectHandle $handle) { + PhabricatorUser $user) { return null; } diff --git a/src/applications/repository/mail/PhabricatorRepositoryPushReplyHandler.php b/src/applications/repository/mail/PhabricatorRepositoryPushReplyHandler.php --- a/src/applications/repository/mail/PhabricatorRepositoryPushReplyHandler.php +++ b/src/applications/repository/mail/PhabricatorRepositoryPushReplyHandler.php @@ -8,7 +8,7 @@ } public function getPrivateReplyHandlerEmailAddress( - PhabricatorObjectHandle $handle) { + PhabricatorUser $user) { return null; } diff --git a/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php b/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php --- a/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryPushMailWorker.php @@ -27,6 +27,23 @@ return; } + $targets = id(new PhabricatorRepositoryPushReplyHandler()) + ->setMailReceiver($repository) + ->getMailTargets($email_phids, array()); + foreach ($targets as $target) { + $this->sendMail($target, $repository, $event); + } + } + + private function sendMail( + PhabricatorMailTarget $target, + PhabricatorRepository $repository, + PhabricatorRepositoryPushEvent $event) { + + $task_data = $this->getTaskData(); + $viewer = $target->getViewer(); + // TODO: Swap locale to viewer locale. + $logs = $event->getLogs(); list($ref_lines, $ref_list) = $this->renderRefs($logs); @@ -103,20 +120,7 @@ ->addHeader('Thread-Topic', $subject) ->setIsBulk(true); - $to_handles = id(new PhabricatorHandleQuery()) - ->setViewer($viewer) - ->withPHIDs($email_phids) - ->execute(); - - $reply_handler = new PhabricatorRepositoryPushReplyHandler(); - $mails = $reply_handler->multiplexMail( - $mail, - $to_handles, - array()); - - foreach ($mails as $mail) { - $mail->saveAndSend(); - } + $target->sendMail($mail); } public function renderForDisplay(PhabricatorUser $viewer) { diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -992,12 +992,10 @@ // Hook for other edges that may need (re-)loading $object = $this->willPublish($object, $xactions); - $this->loadHandles($xactions); - - $mail = null; + $mailed = array(); if (!$this->getDisableEmail()) { if ($this->shouldSendMail($object, $xactions)) { - $mail = $this->sendMail($object, $xactions); + $mailed = $this->sendMail($object, $xactions); } } @@ -1009,10 +1007,6 @@ } if ($this->shouldPublishFeedStory($object, $xactions)) { - $mailed = array(); - if ($mail) { - $mailed = $mail->buildRecipientList(); - } $this->publishFeedStory( $object, $xactions, @@ -2128,30 +2122,60 @@ } if (!$any_visible) { - return; + return array(); } - $email_force = array(); $email_to = $this->mailToPHIDs; $email_cc = $this->mailCCPHIDs; - $email_cc = array_merge($email_cc, $this->heraldEmailPHIDs); - $email_force = $this->heraldForcedEmailPHIDs; - $phids = array_merge($email_to, $email_cc); - $handles = id(new PhabricatorHandleQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs($phids) - ->execute(); + $targets = $this->buildReplyHandler($object) + ->getMailTargets($email_to, $email_cc); + + // Set this explicitly before we start swapping out the effective actor. + $this->setActingAsPHID($this->getActingAsPHID()); + + + $mailed = array(); + foreach ($targets as $target) { + $original_actor = $this->getActor(); + $this->setActor($target->getViewer()); + // TODO: Swap locale to viewer locale. + + $caught = null; + try { + // Reload handles for the new viewer. + $this->loadHandles($xactions); + + $mail = $this->sendMailToTarget($object, $xactions, $target); + } catch (Exception $ex) { + $caught = $ex; + } - $template = $this->buildMailTemplate($object); + $this->setActor($original_actor); + if ($caught) { + throw $ex; + } + + foreach ($mail->buildRecipientList() as $phid) { + $mailed[$phid] = true; + } + } + + return array_keys($mailed); + } + + private function sendMailToTarget( + PhabricatorLiskDAO $object, + array $xactions, + PhabricatorMailTarget $target) { + + $mail = $this->buildMailTemplate($object); $body = $this->buildMailBody($object, $xactions); $mail_tags = $this->getMailTags($object, $xactions); $action = $this->getMailAction($object, $xactions); - $reply_handler = $this->buildReplyHandler($object); - if (PhabricatorEnv::getEnvConfig('metamta.email-preferences')) { $this->addEmailPreferenceSectionToMailBody( $body, @@ -2159,48 +2183,36 @@ $xactions); } - $template + $mail ->setFrom($this->getActingAsPHID()) ->setSubjectPrefix($this->getMailSubjectPrefix()) ->setVarySubjectPrefix('['.$action.']') ->setThreadID($this->getMailThreadID($object), $this->getIsNewObject()) ->setRelatedPHID($object->getPHID()) ->setExcludeMailRecipientPHIDs($this->getExcludeMailRecipientPHIDs()) - ->setForceHeraldMailRecipientPHIDs($email_force) + ->setForceHeraldMailRecipientPHIDs($this->heraldForcedEmailPHIDs) ->setMailTags($mail_tags) ->setIsBulk(true) ->setBody($body->render()) ->setHTMLBody($body->renderHTML()); foreach ($body->getAttachments() as $attachment) { - $template->addAttachment($attachment); + $mail->addAttachment($attachment); } if ($this->heraldHeader) { - $template->addHeader('X-Herald-Rules', $this->heraldHeader); + $mail->addHeader('X-Herald-Rules', $this->heraldHeader); } if ($object instanceof PhabricatorProjectInterface) { - $this->addMailProjectMetadata($object, $template); + $this->addMailProjectMetadata($object, $mail); } if ($this->getParentMessageID()) { - $template->setParentMessageID($this->getParentMessageID()); + $mail->setParentMessageID($this->getParentMessageID()); } - $mails = $reply_handler->multiplexMail( - $template, - array_select_keys($handles, $email_to), - array_select_keys($handles, $email_cc)); - - foreach ($mails as $mail) { - $mail->saveAndSend(); - } - - $template->addTos($email_to); - $template->addCCs($email_cc); - - return $template; + return $target->sendMail($mail); } private function addMailProjectMetadata( diff --git a/src/applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php b/src/applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php --- a/src/applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php +++ b/src/applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php @@ -6,9 +6,9 @@ abstract public function getObjectPrefix(); public function getPrivateReplyHandlerEmailAddress( - PhabricatorObjectHandle $handle) { + PhabricatorUser $user) { return $this->getDefaultPrivateReplyHandlerEmailAddress( - $handle, + $user, $this->getObjectPrefix()); }