1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 23:02:42 +01:00

Legalpad V0.2 - add mail integration

Summary:
Supports !unsubscribe and commenting on replies. Subscribers get mailed something reasonable. Fixes T3480.

Sneaks in /LX/ support. In the near future I want to have that /LX/ be a clean "signature" page sans all the edit actions and other fluff... Will resolve this as part of T3481.

Test Plan: used the metamta console to add comments and unsubscribe. added a phlog() inside mail code to verify mail bodies looked okay.

Reviewers: epriestley

Reviewed By: epriestley

CC: aran, Korvin

Maniphest Tasks: T3480

Differential Revision: https://secure.phabricator.com/D6369
This commit is contained in:
Bob Trahan 2013-07-03 16:37:05 -07:00
parent ffbad23994
commit 2c03cd931b
12 changed files with 272 additions and 2 deletions

View file

@ -0,0 +1,25 @@
<?php
echo "Populating Legalpad Documents with mail keys...\n";
$table = new LegalpadDocument();
$table->openTransaction();
foreach (new LiskMigrationIterator($table) as $document) {
$id = $document->getID();
echo "Document {$id}: ";
if (!$document->getMailKey()) {
queryfx(
$document->establishConnection('w'),
'UPDATE %T SET mailKey = %s WHERE id = %d',
$document->getTableName(),
Filesystem::readRandomCharacters(20),
$id);
echo "Generated Key\n";
} else {
echo "-\n";
}
}
$table->saveTransaction();
echo "Done.\n";

View file

@ -0,0 +1,2 @@
ALTER TABLE `{$NAMESPACE}_legalpad`.legalpad_document
ADD mailKey VARCHAR(20) NOT NULL COLLATE utf8_bin;

View file

@ -644,6 +644,8 @@ phutil_register_library_map(array(
'LegalpadDocumentSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSearchEngine.php', 'LegalpadDocumentSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSearchEngine.php',
'LegalpadDocumentSignature' => 'applications/legalpad/storage/LegalpadDocumentSignature.php', 'LegalpadDocumentSignature' => 'applications/legalpad/storage/LegalpadDocumentSignature.php',
'LegalpadDocumentViewController' => 'applications/legalpad/controller/LegalpadDocumentViewController.php', 'LegalpadDocumentViewController' => 'applications/legalpad/controller/LegalpadDocumentViewController.php',
'LegalpadMockMailReceiver' => 'applications/legalpad/mail/LegalpadMockMailReceiver.php',
'LegalpadReplyHandler' => 'applications/legalpad/mail/LegalpadReplyHandler.php',
'LegalpadTransaction' => 'applications/legalpad/storage/LegalpadTransaction.php', 'LegalpadTransaction' => 'applications/legalpad/storage/LegalpadTransaction.php',
'LegalpadTransactionComment' => 'applications/legalpad/storage/LegalpadTransactionComment.php', 'LegalpadTransactionComment' => 'applications/legalpad/storage/LegalpadTransactionComment.php',
'LegalpadTransactionQuery' => 'applications/legalpad/query/LegalpadTransactionQuery.php', 'LegalpadTransactionQuery' => 'applications/legalpad/query/LegalpadTransactionQuery.php',
@ -1169,6 +1171,7 @@ phutil_register_library_map(array(
'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php', 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php',
'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php', 'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php',
'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php', 'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php',
'PhabricatorLegalpadConfigOptions' => 'applications/legalpad/config/PhabricatorLegalpadConfigOptions.php',
'PhabricatorLintEngine' => 'infrastructure/lint/PhabricatorLintEngine.php', 'PhabricatorLintEngine' => 'infrastructure/lint/PhabricatorLintEngine.php',
'PhabricatorLipsumArtist' => 'applications/lipsum/image/PhabricatorLipsumArtist.php', 'PhabricatorLipsumArtist' => 'applications/lipsum/image/PhabricatorLipsumArtist.php',
'PhabricatorLipsumGenerateWorkflow' => 'applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php', 'PhabricatorLipsumGenerateWorkflow' => 'applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php',
@ -2572,6 +2575,8 @@ phutil_register_library_map(array(
'LegalpadDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine', 'LegalpadDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine',
'LegalpadDocumentSignature' => 'LegalpadDAO', 'LegalpadDocumentSignature' => 'LegalpadDAO',
'LegalpadDocumentViewController' => 'LegalpadController', 'LegalpadDocumentViewController' => 'LegalpadController',
'LegalpadMockMailReceiver' => 'PhabricatorObjectMailReceiver',
'LegalpadReplyHandler' => 'PhabricatorMailReplyHandler',
'LegalpadTransaction' => 'PhabricatorApplicationTransaction', 'LegalpadTransaction' => 'PhabricatorApplicationTransaction',
'LegalpadTransactionComment' => 'PhabricatorApplicationTransactionComment', 'LegalpadTransactionComment' => 'PhabricatorApplicationTransactionComment',
'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
@ -3120,6 +3125,7 @@ phutil_register_library_map(array(
'PhabricatorInlineSummaryView' => 'AphrontView', 'PhabricatorInlineSummaryView' => 'AphrontView',
'PhabricatorJavelinLinter' => 'ArcanistLinter', 'PhabricatorJavelinLinter' => 'ArcanistLinter',
'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache', 'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache',
'PhabricatorLegalpadConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorLintEngine' => 'PhutilLintEngine', 'PhabricatorLintEngine' => 'PhutilLintEngine',
'PhabricatorLipsumGenerateWorkflow' => 'PhabricatorLipsumManagementWorkflow', 'PhabricatorLipsumGenerateWorkflow' => 'PhabricatorLipsumManagementWorkflow',
'PhabricatorLipsumManagementWorkflow' => 'PhutilArgumentWorkflow', 'PhabricatorLipsumManagementWorkflow' => 'PhutilArgumentWorkflow',

View file

@ -40,6 +40,7 @@ final class PhabricatorApplicationLegalpad extends PhabricatorApplication {
public function getRoutes() { public function getRoutes() {
return array( return array(
'/L(?P<id>\d+)/' => 'LegalpadDocumentViewController',
'/legalpad/' => array( '/legalpad/' => array(
'' => 'LegalpadDocumentListController', '' => 'LegalpadDocumentListController',
'(query/(?P<queryKey>[^/]+)/)?' => 'LegalpadDocumentListController', '(query/(?P<queryKey>[^/]+)/)?' => 'LegalpadDocumentListController',

View file

@ -0,0 +1,27 @@
<?php
/**
* @group legalpad
*/
final class PhabricatorLegalpadConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Legalpad');
}
public function getDescription() {
return pht('Configure Legalpad.');
}
public function getOptions() {
return array(
$this->newOption(
'metamta.legalpad.subject-prefix',
'string',
'[Legalpad]')
->setDescription(pht('Subject prefix for Legalpad email.'))
);
}
}

View file

@ -22,6 +22,7 @@ final class LegalpadDocumentCommentController extends LegalpadController {
$document = id(new LegalpadDocumentQuery()) $document = id(new LegalpadDocumentQuery())
->setViewer($user) ->setViewer($user)
->withIDs(array($this->id)) ->withIDs(array($this->id))
->needDocumentBodies(true)
->executeOne(); ->executeOne();
if (!$document) { if (!$document) {

View file

@ -111,10 +111,65 @@ final class LegalpadDocumentEditor
return parent::mergeTransactions($u, $v); return parent::mergeTransactions($u, $v);
} }
/* -( Sending Mail )------------------------------------------------------- */
protected function supportsMail() { protected function supportsMail() {
return false; return true;
} }
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
return id(new LegalpadReplyHandler())
->setMailReceiver($object);
}
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$phid = $object->getPHID();
$title = $object->getDocumentBody()->getTitle();
return id(new PhabricatorMetaMTAMail())
->setSubject("L{$id}: {$title}")
->addHeader('Thread-Topic', "L{$id}: {$phid}");
}
protected function getMailTo(PhabricatorLiskDAO $object) {
return array(
$object->getCreatorPHID(),
$this->requireActor()->getPHID(),
);
}
protected function shouldImplyCC(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case LegalpadTransactionType::TYPE_TEXT:
case LegalpadTransactionType::TYPE_TITLE:
return true;
}
return parent::shouldImplyCC($object, $xaction);
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
$body = parent::buildMailBody($object, $xactions);
$body->addTextSection(
pht('DOCUMENT DETAIL'),
PhabricatorEnv::getProductionURI('/L'.$object->getID()));
return $body;
}
protected function getMailSubjectPrefix() {
return PhabricatorEnv::getEnvConfig('metamta.legalpad.subject-prefix');
}
protected function supportsFeed() { protected function supportsFeed() {
return false; return false;
} }

View file

@ -0,0 +1,38 @@
<?php
final class LegalpadMockMailReceiver extends PhabricatorObjectMailReceiver {
public function isEnabled() {
$app_class = 'PhabricatorApplicationLegalpad';
return PhabricatorApplication::isClassInstalled($app_class);
}
protected function getObjectPattern() {
return 'L[1-9]\d*';
}
protected function loadObject($pattern, PhabricatorUser $viewer) {
$id = (int)trim($pattern, 'L');
return id(new LegalpadDocumentQuery())
->setViewer($viewer)
->withIDs(array($id))
->needDocumentBodies(true)
->executeOne();
}
protected function processReceivedObjectMail(
PhabricatorMetaMTAReceivedMail $mail,
PhabricatorLiskDAO $object,
PhabricatorUser $sender) {
$handler = id(new LegalpadReplyHandler())
->setMailReceiver($object)
->setActor($sender)
->setExcludeMailRecipientPHIDs(
$mail->loadExcludeMailRecipientPHIDs());
return $handler->processEmail($mail);
}
}

View file

@ -0,0 +1,99 @@
<?php
/**
* @group legalpad
*/
final class LegalpadReplyHandler extends PhabricatorMailReplyHandler {
public function validateMailReceiver($mail_receiver) {
if (!($mail_receiver instanceof LegalpadDocument)) {
throw new Exception("Mail receiver is not a LegalpadDocument!");
}
}
public function getPrivateReplyHandlerEmailAddress(
PhabricatorObjectHandle $handle) {
return $this->getDefaultPrivateReplyHandlerEmailAddress($handle, 'L');
}
public function getPublicReplyHandlerEmailAddress() {
return $this->getDefaultPublicReplyHandlerEmailAddress('L');
}
public function getReplyHandlerDomain() {
return PhabricatorEnv::getEnvConfig(
'metamta.reply-handler-domain');
}
public function getReplyHandlerInstructions() {
if ($this->supportsReplies()) {
return 'Reply to comment or !unsubscribe.';
} else {
return null;
}
}
protected function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) {
$actor = $this->getActor();
$document = $this->getMailReceiver();
$body = $mail->getCleanTextBody();
$body = trim($body);
$body = $this->enhanceBodyWithAttachments($body, $mail->getAttachments());
$content_source = PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_EMAIL,
array(
'id' => $mail->getID(),
));
$lines = explode("\n", trim($body));
$first_line = head($lines);
$xactions = array();
$command = null;
$matches = null;
if (preg_match('/^!(\w+)/', $first_line, $matches)) {
$lines = array_slice($lines, 1);
$body = implode("\n", $lines);
$body = trim($body);
$command = $matches[1];
}
switch ($command) {
case 'unsubscribe':
$xaction = id(new LegalpadTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
->setNewValue(array('-' => array($actor->getPHID())));
$xactions[] = $xaction;
break;
}
$xactions[] = id(new LegalpadTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment(
id(new LegalpadTransactionComment())
->setDocumentID($document->getID())
->setLineNumber(0)
->setLineLength(0)
->setContent($body));
$editor = id(new LegalpadDocumentEditor())
->setActor($actor)
->setContentSource($content_source)
->setContinueOnNoEffect(true)
->setIsPreview(false);
try {
$xactions = $editor->applyTransactions($document, $xactions);
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
// just do nothing, though unclear why you're sending a blank email
return true;
}
$head_xaction = head($xactions);
return $head_xaction->getID();
}
}

View file

@ -15,6 +15,7 @@ final class LegalpadDocument extends LegalpadDAO
protected $documentBodyPHID; protected $documentBodyPHID;
protected $viewPolicy; protected $viewPolicy;
protected $editPolicy; protected $editPolicy;
protected $mailKey;
private $documentBody; private $documentBody;
private $contributors; private $contributors;
@ -58,6 +59,13 @@ final class LegalpadDocument extends LegalpadDAO
return $this; return $this;
} }
public function save() {
if (!$this->getMailKey()) {
$this->setMailKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
}
/* -( PhabricatorSubscribableInterface Implementation )-------------------- */ /* -( PhabricatorSubscribableInterface Implementation )-------------------- */
public function isAutomaticallySubscribed($phid) { public function isAutomaticallySubscribed($phid) {

View file

@ -213,7 +213,7 @@ final class PhabricatorObjectHandleData {
case PhabricatorPHIDConstants::PHID_TYPE_LEGD: case PhabricatorPHIDConstants::PHID_TYPE_LEGD:
$legds = id(new LegalpadDocumentQuery()) $legds = id(new LegalpadDocumentQuery())
->needDocumentBody(true) ->needDocumentBodies(true)
->withPHIDs($phids) ->withPHIDs($phids)
->setViewer($this->viewer) ->setViewer($this->viewer)
->execute(); ->execute();

View file

@ -1414,6 +1414,14 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql', 'type' => 'sql',
'name' => $this->getPatchPath('20130701.conduitlog.sql'), 'name' => $this->getPatchPath('20130701.conduitlog.sql'),
), ),
'legalpad-mailkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('legalpad-mailkey.sql'),
),
'legalpad-mailkey-populate.php' => array(
'type' => 'php',
'name' => $this->getPatchPath('legalpad-mailkey-populate.php'),
),
); );
} }
} }