mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-09 16:32:39 +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:
parent
ffbad23994
commit
2c03cd931b
12 changed files with 272 additions and 2 deletions
25
resources/sql/patches/legalpad-mailkey-populate.php
Normal file
25
resources/sql/patches/legalpad-mailkey-populate.php
Normal 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";
|
2
resources/sql/patches/legalpad-mailkey.sql
Normal file
2
resources/sql/patches/legalpad-mailkey.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE `{$NAMESPACE}_legalpad`.legalpad_document
|
||||
ADD mailKey VARCHAR(20) NOT NULL COLLATE utf8_bin;
|
|
@ -644,6 +644,8 @@ phutil_register_library_map(array(
|
|||
'LegalpadDocumentSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSearchEngine.php',
|
||||
'LegalpadDocumentSignature' => 'applications/legalpad/storage/LegalpadDocumentSignature.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',
|
||||
'LegalpadTransactionComment' => 'applications/legalpad/storage/LegalpadTransactionComment.php',
|
||||
'LegalpadTransactionQuery' => 'applications/legalpad/query/LegalpadTransactionQuery.php',
|
||||
|
@ -1169,6 +1171,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php',
|
||||
'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php',
|
||||
'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php',
|
||||
'PhabricatorLegalpadConfigOptions' => 'applications/legalpad/config/PhabricatorLegalpadConfigOptions.php',
|
||||
'PhabricatorLintEngine' => 'infrastructure/lint/PhabricatorLintEngine.php',
|
||||
'PhabricatorLipsumArtist' => 'applications/lipsum/image/PhabricatorLipsumArtist.php',
|
||||
'PhabricatorLipsumGenerateWorkflow' => 'applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php',
|
||||
|
@ -2572,6 +2575,8 @@ phutil_register_library_map(array(
|
|||
'LegalpadDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'LegalpadDocumentSignature' => 'LegalpadDAO',
|
||||
'LegalpadDocumentViewController' => 'LegalpadController',
|
||||
'LegalpadMockMailReceiver' => 'PhabricatorObjectMailReceiver',
|
||||
'LegalpadReplyHandler' => 'PhabricatorMailReplyHandler',
|
||||
'LegalpadTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'LegalpadTransactionComment' => 'PhabricatorApplicationTransactionComment',
|
||||
'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
|
@ -3120,6 +3125,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorInlineSummaryView' => 'AphrontView',
|
||||
'PhabricatorJavelinLinter' => 'ArcanistLinter',
|
||||
'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache',
|
||||
'PhabricatorLegalpadConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorLintEngine' => 'PhutilLintEngine',
|
||||
'PhabricatorLipsumGenerateWorkflow' => 'PhabricatorLipsumManagementWorkflow',
|
||||
'PhabricatorLipsumManagementWorkflow' => 'PhutilArgumentWorkflow',
|
||||
|
|
|
@ -40,6 +40,7 @@ final class PhabricatorApplicationLegalpad extends PhabricatorApplication {
|
|||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/L(?P<id>\d+)/' => 'LegalpadDocumentViewController',
|
||||
'/legalpad/' => array(
|
||||
'' => 'LegalpadDocumentListController',
|
||||
'(query/(?P<queryKey>[^/]+)/)?' => 'LegalpadDocumentListController',
|
||||
|
|
|
@ -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.'))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -22,6 +22,7 @@ final class LegalpadDocumentCommentController extends LegalpadController {
|
|||
$document = id(new LegalpadDocumentQuery())
|
||||
->setViewer($user)
|
||||
->withIDs(array($this->id))
|
||||
->needDocumentBodies(true)
|
||||
->executeOne();
|
||||
|
||||
if (!$document) {
|
||||
|
|
|
@ -111,10 +111,65 @@ final class LegalpadDocumentEditor
|
|||
return parent::mergeTransactions($u, $v);
|
||||
}
|
||||
|
||||
/* -( Sending Mail )------------------------------------------------------- */
|
||||
|
||||
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() {
|
||||
return false;
|
||||
}
|
||||
|
|
38
src/applications/legalpad/mail/LegalpadMockMailReceiver.php
Normal file
38
src/applications/legalpad/mail/LegalpadMockMailReceiver.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
99
src/applications/legalpad/mail/LegalpadReplyHandler.php
Normal file
99
src/applications/legalpad/mail/LegalpadReplyHandler.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -15,6 +15,7 @@ final class LegalpadDocument extends LegalpadDAO
|
|||
protected $documentBodyPHID;
|
||||
protected $viewPolicy;
|
||||
protected $editPolicy;
|
||||
protected $mailKey;
|
||||
|
||||
private $documentBody;
|
||||
private $contributors;
|
||||
|
@ -58,6 +59,13 @@ final class LegalpadDocument extends LegalpadDAO
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function save() {
|
||||
if (!$this->getMailKey()) {
|
||||
$this->setMailKey(Filesystem::readRandomCharacters(20));
|
||||
}
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
/* -( PhabricatorSubscribableInterface Implementation )-------------------- */
|
||||
|
||||
public function isAutomaticallySubscribed($phid) {
|
||||
|
|
|
@ -213,7 +213,7 @@ final class PhabricatorObjectHandleData {
|
|||
|
||||
case PhabricatorPHIDConstants::PHID_TYPE_LEGD:
|
||||
$legds = id(new LegalpadDocumentQuery())
|
||||
->needDocumentBody(true)
|
||||
->needDocumentBodies(true)
|
||||
->withPHIDs($phids)
|
||||
->setViewer($this->viewer)
|
||||
->execute();
|
||||
|
|
|
@ -1414,6 +1414,14 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
|||
'type' => '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'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue