mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-25 16:22:43 +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:
parent
4a76174118
commit
162f34b8c8
8 changed files with 316 additions and 23 deletions
|
@ -313,6 +313,9 @@ return array(
|
|||
|
||||
'differential.revision-custom-detail-renderer' => null,
|
||||
|
||||
'phabricator.enable-reply-handling' => false,
|
||||
'differential.replyhandler' => 'DifferentialReplyHandler',
|
||||
|
||||
|
||||
// -- Maniphest ------------------------------------------------------------- //
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
12
src/applications/differential/mail/exception/__init__.php
Normal file
12
src/applications/differential/mail/exception/__init__.php
Normal 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');
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
15
src/applications/differential/replyhandler/__init__.php
Normal file
15
src/applications/differential/replyhandler/__init__.php
Normal 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');
|
Loading…
Reference in a new issue