1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-29 10:12:41 +01:00

Add reply handler for differential revision

Summary:
add email reply handler so that the user can reply to a
differential email to act on the revision. It generates the reply-to
email address, creates email body text with supported commands list, and
handle the action request on the differential revision.

Right now the reply-to handing is disabled in the config file. But a
site using Phabricator can enable it and implement a class
inheriting from DifferentialReplyHandler to enable customized email
handing.

Later we will need to add code to DifferentialMail.php to support
sending separate email to each email recipient to achieve better
security (see D226). The reply-to will be something like
D<revision_id>+<user_id>+<hash>@domain.com. We will create separate task
for it.

Test Plan:
tried comment on a revision from web UI and the email was
sent out as before without any change. When a subclass of
DifferentialReplyHandler is implemented and enabled, email's reply-to is
set and email text is added. Reply to the email with valid command did
create action to the revision.

Reviewed By: epriestley
Reviewers: tuomaspelkonen, epriestley, slawekbiel, dpepper
CC: aran, epriestley, jungejason
Differential Revision: 224
This commit is contained in:
jungejason 2011-04-30 19:40:05 -07:00 committed by jungejason
parent 4a76174118
commit 162f34b8c8
8 changed files with 316 additions and 23 deletions

View file

@ -313,6 +313,9 @@ return array(
'differential.revision-custom-detail-renderer' => null,
'phabricator.enable-reply-handling' => false,
'differential.replyhandler' => 'DifferentialReplyHandler',
// -- Maniphest ------------------------------------------------------------- //

View file

@ -137,6 +137,7 @@ phutil_register_library_map(array(
'DifferentialDiffProperty' => 'applications/differential/storage/diffproperty',
'DifferentialDiffTableOfContentsView' => 'applications/differential/view/difftableofcontents',
'DifferentialDiffViewController' => 'applications/differential/controller/diffview',
'DifferentialExceptionMail' => 'applications/differential/mail/exception',
'DifferentialHunk' => 'applications/differential/storage/hunk',
'DifferentialInlineComment' => 'applications/differential/storage/inlinecomment',
'DifferentialInlineCommentEditController' => 'applications/differential/controller/inlinecommentedit',
@ -146,6 +147,7 @@ phutil_register_library_map(array(
'DifferentialMail' => 'applications/differential/mail/base',
'DifferentialMarkupEngineFactory' => 'applications/differential/parser/markup',
'DifferentialNewDiffMail' => 'applications/differential/mail/newdiff',
'DifferentialReplyHandler' => 'applications/differential/replyhandler',
'DifferentialReviewRequestMail' => 'applications/differential/mail/reviewrequest',
'DifferentialRevision' => 'applications/differential/storage/revision',
'DifferentialRevisionCommentListView' => 'applications/differential/view/revisioncommentlist',
@ -601,6 +603,7 @@ phutil_register_library_map(array(
'DifferentialDiffProperty' => 'DifferentialDAO',
'DifferentialDiffTableOfContentsView' => 'AphrontView',
'DifferentialDiffViewController' => 'DifferentialController',
'DifferentialExceptionMail' => 'DifferentialMail',
'DifferentialHunk' => 'DifferentialDAO',
'DifferentialInlineComment' => 'DifferentialDAO',
'DifferentialInlineCommentEditController' => 'DifferentialController',

View file

@ -33,6 +33,7 @@ abstract class DifferentialMail {
protected $isFirstMailToRecipients;
protected $heraldTranscriptURI;
protected $heraldRulesHeader;
protected $replyHandler;
abstract protected function renderSubject();
abstract protected function renderBody();
@ -70,16 +71,24 @@ abstract class DifferentialMail {
$body = $this->buildBody();
$mail = new PhabricatorMetaMTAMail();
$handle = $this->getActorHandle();
$reply = $this->getReplyHandlerEmailAddress();
if ($handle) {
$mail->setFrom($handle->getPHID());
if ($reply) {
$mail->setReplyTo($this->getReplyHandlerEmailAddress());
$actor_handle = $this->getActorHandle();
$reply_handler = $this->getReplyHandler();
if ($actor_handle) {
$mail->setFrom($actor_handle->getPHID());
}
if ($reply_handler) {
if ($actor_handle) {
$actor = id(new PhabricatorUser())->loadOneWhere(
'phid = %s',
$actor_handle->getPHID());
$reply_handler->setActor($actor);
}
} else {
if ($reply) {
$mail->setFrom($this->getReplyHandlerEmailAddress());
$reply_to = $reply_handler->getReplyHandlerEmailAddress();
if ($reply_to) {
$mail->setReplyTo($reply_to);
}
}
@ -114,16 +123,12 @@ abstract class DifferentialMail {
protected function buildBody() {
$actions = array();
$body = $this->renderBody();
/*
$body .= <<<EOTEXT
ACTIONS
Reply to comment, or !accept, !reject, !abandon, !resign, or !showdiff.
EOTEXT;
*/
$handler_body_text = $this->getReplyHandlerBodyText();
if ($handler_body_text) {
$body .= $handler_body_text;
}
if ($this->getHeraldTranscriptURI() && $this->isFirstMailToRecipients()) {
$manage_uri = PhabricatorEnv::getProductionURI(
@ -146,12 +151,45 @@ EOTEXT;
return $body;
}
protected function getReplyHandlerEmailAddress() {
return null;
// TODO
$phid = $this->getRevision()->getPHID();
$server = 'todo.example.com';
return "differential+{$phid}@{$server}";
protected function getReplyHandlerBodyText() {
$reply_handler = $this->getReplyHandler();
if (!$reply_handler) {
return null;
}
return $reply_handler->getBodyText();
}
protected function getReplyHandler() {
if ($this->replyHandler) {
return $this->replyHandler;
}
$reply_handler = self::loadReplyHandler();
if (!$reply_handler) {
return null;
}
$reply_handler->setRevision($this->getRevision());
$this->replyHandler = $reply_handler;
return $this->replyHandler;
}
public static function loadReplyHandler() {
if (!PhabricatorEnv::getEnvConfig('phabricator.enable-reply-handling')) {
return null;
}
$reply_handler = PhabricatorEnv::getEnvConfig('differential.replyhandler');
if (!$reply_handler) {
return null;
}
PhutilSymbolLoader::loadClass($reply_handler);
$reply_handler = newv($reply_handler, array());
return $reply_handler;
}
protected function formatText($text) {

View file

@ -7,7 +7,11 @@
phutil_require_module('phabricator', 'applications/metamta/storage/mail');
phutil_require_module('phabricator', 'applications/people/storage/user');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phutil', 'symbols');
phutil_require_module('phutil', 'utils');
phutil_require_source('DifferentialMail.php');

View file

@ -0,0 +1,62 @@
<?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.
*/
class DifferentialExceptionMail extends DifferentialMail {
public function __construct(
DifferentialRevision $revision,
Exception $exception,
$original_body) {
$this->revision = $revision;
$this->exception = $exception;
$this->originalBody = $original_body;
}
protected function renderBody() {
// Never called since buildBody() is overridden.
}
protected function renderSubject() {
return "Exception: unable to process your mail request.";
}
protected function buildBody() {
$exception = $this->exception;
$original_body = $this->originalBody;
$message = $exception->getMessage();
$trace = $exception->getTraceAsString();
return <<<EOBODY
Your request failed because an exception was encoutered while processing it:
EXCEPTION: {$message}
{$trace}
-- Original Body --------------------------
{$original_body}
EOBODY;
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/differential/mail/base');
phutil_require_source('DifferentialExceptionMail.php');

View file

@ -0,0 +1,156 @@
<?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.
*/
class DifferentialReplyHandler {
protected $revision;
protected $actor;
/*
* Generate text like the following from the supported commands.
* "
*
* ACTIONS
* Reply to comment, or !accept, !reject, !abandon, !resign, !reclaim.
*
* "
*/
public function getBodyText() {
$supported_commands = $this->getSupportedCommands();
$text = '';
if (empty($supported_commands)) {
return $text;
}
$comment_command_printed = false;
$text .= "\nACTIONS\n";
if (in_array(DifferentialAction::ACTION_COMMENT, $supported_commands)) {
$text .= '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 .= ".\n\n";
return $text;
}
public function getSupportedCommands() {
return array(
DifferentialAction::ACTION_COMMENT,
DifferentialAction::ACTION_ACCEPT,
DifferentialAction::ACTION_REJECT,
DifferentialAction::ACTION_ABANDON,
DifferentialAction::ACTION_RECLAIM,
DifferentialAction::ACTION_RESIGN,
);
}
public function getReplyHandlerEmailAddress() {
if (!self::isEnabled()) {
return null;
}
$revision = $this->getRevision();
if (!$revision) {
return null;
}
return '...'; // TODO: build the D1234+92+aldsbn@domain.com as per D226
}
public function handleAction($body) {
// 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.');
}
try {
$editor = new DifferentialCommentEditor(
$this->getRevision(),
$actor->getPHID(),
$command);
$editor->setMessage($body);
$editor->setAddCC(($command != DifferentialAction::ACTION_RESIGN));
$comment = $editor->save();
return $comment->getID();
} catch (Exception $ex) {
$exception_mail = new DifferentialExceptionMail(
$this->getRevision(),
$ex,
$body);
$exception_mail->setToPHIDs(array($this->getActor()->getPHID()));
$exception_mail->send();
throw $ex;
}
}
public function setActor(PhabricatorUser $actor) {
$this->actor = $actor;
return $this;
}
public function getActor() {
return $this->actor;
}
public function setRevision(DifferentialRevision $revision) {
$this->revision = $revision;
return $this;
}
public function getRevision() {
return $this->revision;
}
public static function isEnabled() {
return PhabricatorEnv::getEnvConfig('phabricator.enable-reply-handling');
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/differential/constants/action');
phutil_require_module('phabricator', 'applications/differential/editor/comment');
phutil_require_module('phabricator', 'applications/differential/mail/exception');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_source('DifferentialReplyHandler.php');