diff --git a/resources/sql/patches/legalpad-mailkey-populate.php b/resources/sql/patches/legalpad-mailkey-populate.php new file mode 100644 index 0000000000..e754ab2757 --- /dev/null +++ b/resources/sql/patches/legalpad-mailkey-populate.php @@ -0,0 +1,25 @@ +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"; diff --git a/resources/sql/patches/legalpad-mailkey.sql b/resources/sql/patches/legalpad-mailkey.sql new file mode 100644 index 0000000000..b416f3518f --- /dev/null +++ b/resources/sql/patches/legalpad-mailkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE `{$NAMESPACE}_legalpad`.legalpad_document + ADD mailKey VARCHAR(20) NOT NULL COLLATE utf8_bin; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b9478aab7d..3baf4ec98e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/applications/legalpad/application/PhabricatorApplicationLegalpad.php b/src/applications/legalpad/application/PhabricatorApplicationLegalpad.php index ff141819e9..c10ebddbb3 100644 --- a/src/applications/legalpad/application/PhabricatorApplicationLegalpad.php +++ b/src/applications/legalpad/application/PhabricatorApplicationLegalpad.php @@ -40,6 +40,7 @@ final class PhabricatorApplicationLegalpad extends PhabricatorApplication { public function getRoutes() { return array( + '/L(?P\d+)/' => 'LegalpadDocumentViewController', '/legalpad/' => array( '' => 'LegalpadDocumentListController', '(query/(?P[^/]+)/)?' => 'LegalpadDocumentListController', diff --git a/src/applications/legalpad/config/PhabricatorLegalpadConfigOptions.php b/src/applications/legalpad/config/PhabricatorLegalpadConfigOptions.php new file mode 100644 index 0000000000..11f675eda5 --- /dev/null +++ b/src/applications/legalpad/config/PhabricatorLegalpadConfigOptions.php @@ -0,0 +1,27 @@ +newOption( + 'metamta.legalpad.subject-prefix', + 'string', + '[Legalpad]') + ->setDescription(pht('Subject prefix for Legalpad email.')) + ); + } + +} diff --git a/src/applications/legalpad/controller/LegalpadDocumentCommentController.php b/src/applications/legalpad/controller/LegalpadDocumentCommentController.php index ca1e01ff17..a7b9efa66d 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentCommentController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentCommentController.php @@ -22,6 +22,7 @@ final class LegalpadDocumentCommentController extends LegalpadController { $document = id(new LegalpadDocumentQuery()) ->setViewer($user) ->withIDs(array($this->id)) + ->needDocumentBodies(true) ->executeOne(); if (!$document) { diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditor.php b/src/applications/legalpad/editor/LegalpadDocumentEditor.php index 89364aae64..d588eadc33 100644 --- a/src/applications/legalpad/editor/LegalpadDocumentEditor.php +++ b/src/applications/legalpad/editor/LegalpadDocumentEditor.php @@ -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; } diff --git a/src/applications/legalpad/mail/LegalpadMockMailReceiver.php b/src/applications/legalpad/mail/LegalpadMockMailReceiver.php new file mode 100644 index 0000000000..e6739049a9 --- /dev/null +++ b/src/applications/legalpad/mail/LegalpadMockMailReceiver.php @@ -0,0 +1,38 @@ +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); + } + +} diff --git a/src/applications/legalpad/mail/LegalpadReplyHandler.php b/src/applications/legalpad/mail/LegalpadReplyHandler.php new file mode 100644 index 0000000000..30befc3316 --- /dev/null +++ b/src/applications/legalpad/mail/LegalpadReplyHandler.php @@ -0,0 +1,99 @@ +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(); + } + +} diff --git a/src/applications/legalpad/storage/LegalpadDocument.php b/src/applications/legalpad/storage/LegalpadDocument.php index c72e7e853f..98b1714eb6 100644 --- a/src/applications/legalpad/storage/LegalpadDocument.php +++ b/src/applications/legalpad/storage/LegalpadDocument.php @@ -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) { diff --git a/src/applications/phid/handle/PhabricatorObjectHandleData.php b/src/applications/phid/handle/PhabricatorObjectHandleData.php index f1572bb9a7..80db253653 100644 --- a/src/applications/phid/handle/PhabricatorObjectHandleData.php +++ b/src/applications/phid/handle/PhabricatorObjectHandleData.php @@ -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(); diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index 9c6bdb8027..44adcaf273 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -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'), + ), ); } }