diff --git a/src/applications/differential/mail/DifferentialReplyHandler.php b/src/applications/differential/mail/DifferentialReplyHandler.php index 2e3b47327c..a8ed6fcb30 100644 --- a/src/applications/differential/mail/DifferentialReplyHandler.php +++ b/src/applications/differential/mail/DifferentialReplyHandler.php @@ -1,173 +1,193 @@ getDefaultPrivateReplyHandlerEmailAddress($handle, 'D'); } public function getPublicReplyHandlerEmailAddress() { return $this->getDefaultPublicReplyHandlerEmailAddress('D'); } public function getReplyHandlerDomain() { return PhabricatorEnv::getEnvConfig( 'metamta.differential.reply-handler-domain'); } /* * Generate text like the following from the supported commands. * " * * ACTIONS * Reply to comment, or !accept, !reject, !abandon, !resign, !reclaim. * * " */ public function getReplyHandlerInstructions() { if (!$this->supportsReplies()) { return null; } $supported_commands = $this->getSupportedCommands(); $text = ''; if (empty($supported_commands)) { return $text; } $comment_command_printed = false; if (in_array(DifferentialAction::ACTION_COMMENT, $supported_commands)) { $text .= pht('Reply to comment'); $comment_command_printed = true; $supported_commands = array_diff( $supported_commands, array(DifferentialAction::ACTION_COMMENT)); } if (!empty($supported_commands)) { if ($comment_command_printed) { $text .= ', or '; } $modified_commands = array(); foreach ($supported_commands as $command) { $modified_commands[] = '!'.$command; } $text .= implode(', ', $modified_commands); } $text .= "."; return $text; } public function getSupportedCommands() { $actions = array( DifferentialAction::ACTION_COMMENT, DifferentialAction::ACTION_REJECT, DifferentialAction::ACTION_ABANDON, DifferentialAction::ACTION_RECLAIM, DifferentialAction::ACTION_RESIGN, DifferentialAction::ACTION_RETHINK, 'unsubscribe', ); if (PhabricatorEnv::getEnvConfig('differential.enable-email-accept')) { $actions[] = DifferentialAction::ACTION_ACCEPT; } return $actions; } protected function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) { $this->receivedMail = $mail; $this->handleAction($mail->getCleanTextBody(), $mail->getAttachments()); } public function handleAction($body, array $attachments) { // all commands start with a bang and separated from the body by a newline // to make sure that actual feedback text couldn't trigger an action. // unrecognized commands will be parsed as part of the comment. $command = DifferentialAction::ACTION_COMMENT; $supported_commands = $this->getSupportedCommands(); $regex = "/\A\n*!(" . implode('|', $supported_commands) . ")\n*/"; $matches = array(); if (preg_match($regex, $body, $matches)) { $command = $matches[1]; $body = trim(str_replace('!' . $command, '', $body)); } $actor = $this->getActor(); if (!$actor) { throw new Exception('No actor is set for the reply action.'); } switch ($command) { case 'unsubscribe': id(new PhabricatorSubscriptionsEditor()) ->setObject($this->getMailReceiver()) ->unsubscribe(array($actor->getPHID())) ->save(); // TODO: Send the user a confirmation email? return null; } $body = $this->enhanceBodyWithAttachments($body, $attachments); + $xactions = array(); + + if ($command && ($command != DifferentialAction::ACTION_COMMENT)) { + $xactions[] = id(new DifferentialTransaction()) + ->setTransactionType(DifferentialTransaction::TYPE_ACTION) + ->setNewValue($command); + } + + if (strlen($body)) { + $xactions[] = id(new DifferentialTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) + ->attachComment( + id(new DifferentialTransactionComment()) + ->setContent($body)); + } + + $editor = id(new DifferentialTransactionEditor()) + ->setActor($actor) + ->setExcludeMailRecipientPHIDs($this->getExcludeMailRecipientPHIDs()) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + // NOTE: We have to be careful about this because Facebook's + // implementation jumps straight into handleAction() and will not have + // a PhabricatorMetaMTAReceivedMail object. + if ($this->receivedMail) { + $content_source = PhabricatorContentSource::newForSource( + PhabricatorContentSource::SOURCE_EMAIL, + array( + 'id' => $this->receivedMail->getID(), + )); + $editor->setContentSource($content_source); + $editor->setParentMessageID($this->receivedMail->getMessageID()); + } else { + $content_source = PhabricatorContentSource::newForSource( + PhabricatorContentSource::SOURCE_LEGACY, + array()); + $editor->setContentSource($content_source); + } + try { - $editor = new DifferentialCommentEditor( - $this->getMailReceiver(), - $command); - $editor->setActor($actor); - $editor->setExcludeMailRecipientPHIDs( - $this->getExcludeMailRecipientPHIDs()); - - // NOTE: We have to be careful about this because Facebook's - // implementation jumps straight into handleAction() and will not have - // a PhabricatorMetaMTAReceivedMail object. - if ($this->receivedMail) { - $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_EMAIL, - array( - 'id' => $this->receivedMail->getID(), - )); - $editor->setContentSource($content_source); - $editor->setParentMessageID($this->receivedMail->getMessageID()); - } - $editor->setMessage($body); - $editor->save(); + $editor->applyTransactions($this->getMailReceiver(), $xactions); } catch (Exception $ex) { if ($this->receivedMail) { $error_body = $this->receivedMail->getRawTextBody(); } else { $error_body = $body; } $exception_mail = new DifferentialExceptionMail( $this->getMailReceiver(), $ex, $error_body); $exception_mail->setActor($this->getActor()); $exception_mail->setToPHIDs(array($this->getActor()->getPHID())); $exception_mail->send(); throw $ex; } } } diff --git a/src/applications/differential/mail/DifferentialRevisionMailReceiver.php b/src/applications/differential/mail/DifferentialRevisionMailReceiver.php index 87f63a615e..0568cf186c 100644 --- a/src/applications/differential/mail/DifferentialRevisionMailReceiver.php +++ b/src/applications/differential/mail/DifferentialRevisionMailReceiver.php @@ -1,39 +1,40 @@ setViewer($viewer) ->withIDs(array($id)) + ->needReviewerStatus(true) ->execute(); return head($results); } protected function processReceivedObjectMail( PhabricatorMetaMTAReceivedMail $mail, PhabricatorLiskDAO $object, PhabricatorUser $sender) { $handler = DifferentialMail::newReplyHandlerForRevision($object); $handler->setActor($sender); $handler->setExcludeMailRecipientPHIDs( $mail->loadExcludeMailRecipientPHIDs()); $handler->processEmail($mail); } }