[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 abstract class PhabricatorMailReplyHandler { 4 5 private $mailReceiver; 6 private $actor; 7 private $excludePHIDs = array(); 8 9 final public function setMailReceiver($mail_receiver) { 10 $this->validateMailReceiver($mail_receiver); 11 $this->mailReceiver = $mail_receiver; 12 return $this; 13 } 14 15 final public function getMailReceiver() { 16 return $this->mailReceiver; 17 } 18 19 final public function setActor(PhabricatorUser $actor) { 20 $this->actor = $actor; 21 return $this; 22 } 23 24 final public function getActor() { 25 return $this->actor; 26 } 27 28 final public function setExcludeMailRecipientPHIDs(array $exclude) { 29 $this->excludePHIDs = $exclude; 30 return $this; 31 } 32 33 final public function getExcludeMailRecipientPHIDs() { 34 return $this->excludePHIDs; 35 } 36 37 abstract public function validateMailReceiver($mail_receiver); 38 abstract public function getPrivateReplyHandlerEmailAddress( 39 PhabricatorObjectHandle $handle); 40 public function getReplyHandlerDomain() { 41 return PhabricatorEnv::getEnvConfig( 42 'metamta.reply-handler-domain'); 43 } 44 abstract public function getReplyHandlerInstructions(); 45 abstract protected function receiveEmail( 46 PhabricatorMetaMTAReceivedMail $mail); 47 48 public function processEmail(PhabricatorMetaMTAReceivedMail $mail) { 49 $this->dropEmptyMail($mail); 50 51 return $this->receiveEmail($mail); 52 } 53 54 private function dropEmptyMail(PhabricatorMetaMTAReceivedMail $mail) { 55 $body = $mail->getCleanTextBody(); 56 $attachments = $mail->getAttachments(); 57 58 if (strlen($body) || $attachments) { 59 return; 60 } 61 62 // Only send an error email if the user is talking to just Phabricator. 63 // We can assume if there is only one "To" address it is a Phabricator 64 // address since this code is running and everything. 65 $is_direct_mail = (count($mail->getToAddresses()) == 1) && 66 (count($mail->getCCAddresses()) == 0); 67 68 if ($is_direct_mail) { 69 $status_code = MetaMTAReceivedMailStatus::STATUS_EMPTY; 70 } else { 71 $status_code = MetaMTAReceivedMailStatus::STATUS_EMPTY_IGNORED; 72 } 73 74 throw new PhabricatorMetaMTAReceivedMailProcessingException( 75 $status_code, 76 pht( 77 'Your message does not contain any body text or attachments, so '. 78 'Phabricator can not do anything useful with it. Make sure comment '. 79 'text appears at the top of your message: quoted replies, inline '. 80 'text, and signatures are discarded and ignored.')); 81 } 82 83 public function supportsPrivateReplies() { 84 return (bool)$this->getReplyHandlerDomain() && 85 !$this->supportsPublicReplies(); 86 } 87 88 public function supportsPublicReplies() { 89 if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) { 90 return false; 91 } 92 if (!$this->getReplyHandlerDomain()) { 93 return false; 94 } 95 return (bool)$this->getPublicReplyHandlerEmailAddress(); 96 } 97 98 final public function supportsReplies() { 99 return $this->supportsPrivateReplies() || 100 $this->supportsPublicReplies(); 101 } 102 103 public function getPublicReplyHandlerEmailAddress() { 104 return null; 105 } 106 107 final public function getRecipientsSummary( 108 array $to_handles, 109 array $cc_handles) { 110 assert_instances_of($to_handles, 'PhabricatorObjectHandle'); 111 assert_instances_of($cc_handles, 'PhabricatorObjectHandle'); 112 113 $body = ''; 114 115 if (PhabricatorEnv::getEnvConfig('metamta.recipients.show-hints')) { 116 if ($to_handles) { 117 $body .= "To: ".implode(', ', mpull($to_handles, 'getName'))."\n"; 118 } 119 if ($cc_handles) { 120 $body .= "Cc: ".implode(', ', mpull($cc_handles, 'getName'))."\n"; 121 } 122 } 123 124 return $body; 125 } 126 127 final public function getRecipientsSummaryHTML( 128 array $to_handles, 129 array $cc_handles) { 130 assert_instances_of($to_handles, 'PhabricatorObjectHandle'); 131 assert_instances_of($cc_handles, 'PhabricatorObjectHandle'); 132 133 if (PhabricatorEnv::getEnvConfig('metamta.recipients.show-hints')) { 134 $body = array(); 135 if ($to_handles) { 136 $body[] = phutil_tag('strong', array(), 'To: '); 137 $body[] = phutil_implode_html(', ', mpull($to_handles, 'getName')); 138 $body[] = phutil_tag('br'); 139 } 140 if ($cc_handles) { 141 $body[] = phutil_tag('strong', array(), 'Cc: '); 142 $body[] = phutil_implode_html(', ', mpull($cc_handles, 'getName')); 143 $body[] = phutil_tag('br'); 144 } 145 return phutil_tag('div', array(), $body); 146 } else { 147 return ''; 148 } 149 150 } 151 152 final public function multiplexMail( 153 PhabricatorMetaMTAMail $mail_template, 154 array $to_handles, 155 array $cc_handles) { 156 assert_instances_of($to_handles, 'PhabricatorObjectHandle'); 157 assert_instances_of($cc_handles, 'PhabricatorObjectHandle'); 158 159 $result = array(); 160 161 // If MetaMTA is configured to always multiplex, skip the single-email 162 // case. 163 if (!PhabricatorMetaMTAMail::shouldMultiplexAllMail()) { 164 // If private replies are not supported, simply send one email to all 165 // recipients and CCs. This covers cases where we have no reply handler, 166 // or we have a public reply handler. 167 if (!$this->supportsPrivateReplies()) { 168 $mail = clone $mail_template; 169 $mail->addTos(mpull($to_handles, 'getPHID')); 170 $mail->addCCs(mpull($cc_handles, 'getPHID')); 171 172 if ($this->supportsPublicReplies()) { 173 $reply_to = $this->getPublicReplyHandlerEmailAddress(); 174 $mail->setReplyTo($reply_to); 175 } 176 177 $result[] = $mail; 178 179 return $result; 180 } 181 } 182 183 // TODO: This is pretty messy. We should really be doing all of this 184 // multiplexing in the task queue, but that requires significant rewriting 185 // in the general case. ApplicationTransactions can do it fairly easily, 186 // but other mail sites currently can not, so we need to support this 187 // junky version until they catch up and we can swap things over. 188 189 $to_handles = $this->expandRecipientHandles($to_handles); 190 $cc_handles = $this->expandRecipientHandles($cc_handles); 191 192 $tos = mpull($to_handles, null, 'getPHID'); 193 $ccs = mpull($cc_handles, null, 'getPHID'); 194 195 // Merge all the recipients together. TODO: We could keep the CCs as real 196 // CCs and send to a "[email protected]" type address, but keep it simple 197 // for now. 198 $recipients = $tos + $ccs; 199 200 // When multiplexing mail, explicitly include To/Cc information in the 201 // message body and headers. 202 203 $mail_template = clone $mail_template; 204 205 $mail_template->addPHIDHeaders('X-Phabricator-To', array_keys($tos)); 206 $mail_template->addPHIDHeaders('X-Phabricator-Cc', array_keys($ccs)); 207 208 $body = $mail_template->getBody(); 209 $body .= "\n"; 210 $body .= $this->getRecipientsSummary($to_handles, $cc_handles); 211 212 $html_body = $mail_template->getHTMLBody(); 213 if (strlen($html_body)) { 214 $html_body .= hsprintf('%s', 215 $this->getRecipientsSummaryHTML($to_handles, $cc_handles)); 216 } 217 218 foreach ($recipients as $phid => $recipient) { 219 220 $mail = clone $mail_template; 221 if (isset($to_handles[$phid])) { 222 $mail->addTos(array($phid)); 223 } else if (isset($cc_handles[$phid])) { 224 $mail->addCCs(array($phid)); 225 } else { 226 // not good - they should be a to or a cc 227 continue; 228 } 229 230 $mail->setBody($body); 231 $mail->setHTMLBody($html_body); 232 233 $reply_to = null; 234 if (!$reply_to && $this->supportsPrivateReplies()) { 235 $reply_to = $this->getPrivateReplyHandlerEmailAddress($recipient); 236 } 237 238 if (!$reply_to && $this->supportsPublicReplies()) { 239 $reply_to = $this->getPublicReplyHandlerEmailAddress(); 240 } 241 242 if ($reply_to) { 243 $mail->setReplyTo($reply_to); 244 } 245 246 $result[] = $mail; 247 } 248 249 return $result; 250 } 251 252 protected function getDefaultPublicReplyHandlerEmailAddress($prefix) { 253 254 $receiver = $this->getMailReceiver(); 255 $receiver_id = $receiver->getID(); 256 $domain = $this->getReplyHandlerDomain(); 257 258 // We compute a hash using the object's own PHID to prevent an attacker 259 // from blindly interacting with objects that they haven't ever received 260 // mail about by just sending to D1@, D2@, etc... 261 $hash = PhabricatorObjectMailReceiver::computeMailHash( 262 $receiver->getMailKey(), 263 $receiver->getPHID()); 264 265 $address = "{$prefix}{$receiver_id}+public+{$hash}@{$domain}"; 266 return $this->getSingleReplyHandlerPrefix($address); 267 } 268 269 protected function getSingleReplyHandlerPrefix($address) { 270 $single_handle_prefix = PhabricatorEnv::getEnvConfig( 271 'metamta.single-reply-handler-prefix'); 272 return ($single_handle_prefix) 273 ? $single_handle_prefix.'+'.$address 274 : $address; 275 } 276 277 protected function getDefaultPrivateReplyHandlerEmailAddress( 278 PhabricatorObjectHandle $handle, 279 $prefix) { 280 281 if ($handle->getType() != PhabricatorPeopleUserPHIDType::TYPECONST) { 282 // You must be a real user to get a private reply handler address. 283 return null; 284 } 285 286 $user = id(new PhabricatorPeopleQuery()) 287 ->setViewer(PhabricatorUser::getOmnipotentUser()) 288 ->withPHIDs(array($handle->getPHID())) 289 ->executeOne(); 290 291 if (!$user) { 292 // This may happen if a user was subscribed to something, and was then 293 // deleted. 294 return null; 295 } 296 297 $receiver = $this->getMailReceiver(); 298 $receiver_id = $receiver->getID(); 299 $user_id = $user->getID(); 300 $hash = PhabricatorObjectMailReceiver::computeMailHash( 301 $receiver->getMailKey(), 302 $handle->getPHID()); 303 $domain = $this->getReplyHandlerDomain(); 304 305 $address = "{$prefix}{$receiver_id}+{$user_id}+{$hash}@{$domain}"; 306 return $this->getSingleReplyHandlerPrefix($address); 307 } 308 309 final protected function enhanceBodyWithAttachments( 310 $body, 311 array $attachments, 312 $format = '- {F%d, layout=link}') { 313 if (!$attachments) { 314 return $body; 315 } 316 317 // TODO: (T603) What's the policy here? 318 $files = id(new PhabricatorFile()) 319 ->loadAllWhere('phid in (%Ls)', $attachments); 320 321 // if we have some text then double return before adding our file list 322 if ($body) { 323 $body .= "\n\n"; 324 } 325 326 foreach ($files as $file) { 327 $file_str = sprintf($format, $file->getID()); 328 $body .= $file_str."\n"; 329 } 330 331 return rtrim($body); 332 } 333 334 private function expandRecipientHandles(array $handles) { 335 if (!$handles) { 336 return array(); 337 } 338 339 $phids = mpull($handles, 'getPHID'); 340 $map = id(new PhabricatorMetaMTAMemberQuery()) 341 ->setViewer(PhabricatorUser::getOmnipotentUser()) 342 ->withPHIDs($phids) 343 ->execute(); 344 345 $results = array(); 346 foreach ($phids as $phid) { 347 if (isset($map[$phid])) { 348 foreach ($map[$phid] as $expanded_phid) { 349 $results[$expanded_phid] = $expanded_phid; 350 } 351 } else { 352 $results[$phid] = $phid; 353 } 354 } 355 356 return id(new PhabricatorHandleQuery()) 357 ->setViewer(PhabricatorUser::getOmnipotentUser()) 358 ->withPHIDs($results) 359 ->execute(); 360 } 361 362 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |