[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/metamta/replyhandler/ -> PhabricatorMailReplyHandler.php (source)

   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  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1