diff --git a/resources/sql/patches/20130805.pasteedges.sql b/resources/sql/patches/20130805.pasteedges.sql new file mode 100644 index 0000000000..6a55d8c2bd --- /dev/null +++ b/resources/sql/patches/20130805.pasteedges.sql @@ -0,0 +1,15 @@ +CREATE TABLE {$NAMESPACE}_pastebin.edge ( + src VARCHAR(64) NOT NULL COLLATE utf8_bin, + type VARCHAR(64) NOT NULL COLLATE utf8_bin, + dst VARCHAR(64) NOT NULL COLLATE utf8_bin, + dateCreated INT UNSIGNED NOT NULL, + seq INT UNSIGNED NOT NULL, + dataID INT UNSIGNED, + PRIMARY KEY (src, type, dst), + KEY (src, type, dateCreated, seq) +) ENGINE=InnoDB, COLLATE utf8_general_ci; + +CREATE TABLE {$NAMESPACE}_pastebin.edgedata ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + data LONGTEXT NOT NULL COLLATE utf8_bin +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/resources/sql/patches/20130805.pastemailkey.sql b/resources/sql/patches/20130805.pastemailkey.sql new file mode 100644 index 0000000000..ef1b6523b9 --- /dev/null +++ b/resources/sql/patches/20130805.pastemailkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_pastebin.pastebin_paste + ADD COLUMN `mailKey` varchar(20) NOT NULL; diff --git a/resources/sql/patches/20130805.pastemailkeypop.php b/resources/sql/patches/20130805.pastemailkeypop.php new file mode 100644 index 0000000000..afc98a70cc --- /dev/null +++ b/resources/sql/patches/20130805.pastemailkeypop.php @@ -0,0 +1,27 @@ +openTransaction(); +$conn_w = $table->establishConnection('w'); + +foreach (new LiskMigrationIterator($table) as $paste) { + $id = $paste->getID(); + + echo "P{$id}: "; + if (!$paste->getMailKey()) { + queryfx( + $conn_w, + 'UPDATE %T SET mailKey = %s WHERE id = %d', + $paste->getTableName(), + Filesystem::readRandomCharacters(20), + $id); + echo("Generated Key\n"); + } else { + echo "-\n"; + } +} + +$table->saveTransaction(); +echo "Done.\n"; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9155ea4a8e..4e6c021422 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -759,6 +759,8 @@ phutil_register_library_map(array( 'PackageModifyMail' => 'applications/owners/mail/PackageModifyMail.php', 'PasteCreateMailReceiver' => 'applications/paste/mail/PasteCreateMailReceiver.php', 'PasteEmbedView' => 'applications/paste/view/PasteEmbedView.php', + 'PasteMockMailReceiver' => 'applications/paste/mail/PasteMockMailReceiver.php', + 'PasteReplyHandler' => 'applications/paste/mail/PasteReplyHandler.php', 'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php', 'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php', 'PhabricatorAccessLog' => 'infrastructure/PhabricatorAccessLog.php', @@ -2773,6 +2775,8 @@ phutil_register_library_map(array( 'PackageModifyMail' => 'PackageMail', 'PasteCreateMailReceiver' => 'PhabricatorMailReceiver', 'PasteEmbedView' => 'AphrontView', + 'PasteMockMailReceiver' => 'PhabricatorObjectMailReceiver', + 'PasteReplyHandler' => 'PhabricatorMailReplyHandler', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAWSConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -3417,8 +3421,9 @@ phutil_register_library_map(array( 'PhabricatorPaste' => array( 0 => 'PhabricatorPasteDAO', - 1 => 'PhabricatorTokenReceiverInterface', - 2 => 'PhabricatorPolicyInterface', + 1 => 'PhabricatorSubscribableInterface', + 2 => 'PhabricatorTokenReceiverInterface', + 3 => 'PhabricatorPolicyInterface', ), 'PhabricatorPasteCommentController' => 'PhabricatorPasteController', 'PhabricatorPasteConfigOptions' => 'PhabricatorApplicationConfigOptions', diff --git a/src/applications/paste/config/PhabricatorPasteConfigOptions.php b/src/applications/paste/config/PhabricatorPasteConfigOptions.php index 311cee73e1..49ae200e45 100644 --- a/src/applications/paste/config/PhabricatorPasteConfigOptions.php +++ b/src/applications/paste/config/PhabricatorPasteConfigOptions.php @@ -20,7 +20,12 @@ final class PhabricatorPasteConfigOptions 'metamta.paste.public-create-email', 'string', null) - ->setDescription(pht('Allow creating pastes via email.')) + ->setDescription(pht('Allow creating pastes via email.')), + $this->newOption( + 'metamta.paste.subject-prefix', + 'string', + '[Paste]') + ->setDescription(pht('Subject prefix for paste email.')) ); } diff --git a/src/applications/paste/editor/PhabricatorPasteEditor.php b/src/applications/paste/editor/PhabricatorPasteEditor.php index f7039dded3..88ef508664 100644 --- a/src/applications/paste/editor/PhabricatorPasteEditor.php +++ b/src/applications/paste/editor/PhabricatorPasteEditor.php @@ -101,7 +101,11 @@ final class PhabricatorPasteEditor } protected function supportsMail() { - return false; + return true; + } + + protected function getMailSubjectPrefix() { + return PhabricatorEnv::getEnvConfig('metamta.paste.subject-prefix'); } protected function getMailTo(PhabricatorLiskDAO $object) { @@ -111,8 +115,18 @@ final class PhabricatorPasteEditor ); } - protected function getMailCC(PhabricatorLiskDAO $object) { - return array(); + protected function buildReplyHandler(PhabricatorLiskDAO $object) { + return id(new PasteReplyHandler()) + ->setMailReceiver($object); + } + + protected function buildMailTemplate(PhabricatorLiskDAO $object) { + $id = $object->getID(); + $name = $object->getTitle(); + + return id(new PhabricatorMetaMTAMail()) + ->setSubject("P{$id}: {$name}") + ->addHeader('Thread-Topic', "P{$id}"); } protected function supportsFeed() { diff --git a/src/applications/paste/mail/PasteCreateMailReceiver.php b/src/applications/paste/mail/PasteCreateMailReceiver.php index b55a554575..e5053ea70e 100644 --- a/src/applications/paste/mail/PasteCreateMailReceiver.php +++ b/src/applications/paste/mail/PasteCreateMailReceiver.php @@ -66,6 +66,8 @@ final class PasteCreateMailReceiver $mail->setRelatedPHID($paste->getPHID()); + $subject_prefix = + PhabricatorEnv::getEnvConfig('metamta.paste.subject-prefix'); $subject = pht('You successfully created a paste.'); $paste_uri = PhabricatorEnv::getProductionURI($paste->getURI()); $body = new PhabricatorMetaMTAMailBody(); @@ -74,7 +76,8 @@ final class PasteCreateMailReceiver id(new PhabricatorMetaMTAMail()) ->addTos(array($sender->getPHID())) - ->setSubject('[Paste] '.$subject) + ->setSubject($subject) + ->setSubjectPrefix($subject_prefix) ->setFrom($sender->getPHID()) ->setRelatedPHID($paste->getPHID()) ->setBody($body->render()) diff --git a/src/applications/paste/mail/PasteMockMailReceiver.php b/src/applications/paste/mail/PasteMockMailReceiver.php new file mode 100644 index 0000000000..bab2160abe --- /dev/null +++ b/src/applications/paste/mail/PasteMockMailReceiver.php @@ -0,0 +1,40 @@ +setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + } + + protected function processReceivedObjectMail( + PhabricatorMetaMTAReceivedMail $mail, + PhabricatorLiskDAO $object, + PhabricatorUser $sender) { + + $handler = id(new PasteReplyHandler()) + ->setMailReceiver($object); + + $handler->setActor($sender); + $handler->setExcludeMailRecipientPHIDs( + $mail->loadExcludeMailRecipientPHIDs()); + $handler->processEmail($mail); + } + +} diff --git a/src/applications/paste/mail/PasteReplyHandler.php b/src/applications/paste/mail/PasteReplyHandler.php new file mode 100644 index 0000000000..93f3f79520 --- /dev/null +++ b/src/applications/paste/mail/PasteReplyHandler.php @@ -0,0 +1,92 @@ +getDefaultPrivateReplyHandlerEmailAddress($handle, 'P'); + } + + public function getPublicReplyHandlerEmailAddress() { + return $this->getDefaultPublicReplyHandlerEmailAddress('P'); + } + + public function getReplyHandlerInstructions() { + if ($this->supportsReplies()) { + return pht('Reply to comment or !unsubscribe.'); + } else { + return null; + } + } + + protected function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) { + $actor = $this->getActor(); + $paste = $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 PhabricatorPasteTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) + ->setNewValue(array('-' => array($actor->getPHID()))); + $xactions[] = $xaction; + break; + } + + $xactions[] = id(new PhabricatorPasteTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) + ->attachComment( + id(new PhabricatorPasteTransactionComment()) + ->setContent($body)); + + $editor = id(new PhabricatorPasteEditor()) + ->setActor($actor) + ->setContentSource($content_source) + ->setContinueOnNoEffect(true) + ->setIsPreview(false); + + try { + $xactions = $editor->applyTransactions($paste, $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/paste/storage/PhabricatorPaste.php b/src/applications/paste/storage/PhabricatorPaste.php index 3b4326b84e..df31901698 100644 --- a/src/applications/paste/storage/PhabricatorPaste.php +++ b/src/applications/paste/storage/PhabricatorPaste.php @@ -4,7 +4,10 @@ * @group paste */ final class PhabricatorPaste extends PhabricatorPasteDAO - implements PhabricatorTokenReceiverInterface, PhabricatorPolicyInterface { + implements + PhabricatorSubscribableInterface, + PhabricatorTokenReceiverInterface, + PhabricatorPolicyInterface { protected $phid; protected $title; @@ -13,6 +16,7 @@ final class PhabricatorPaste extends PhabricatorPasteDAO protected $language; protected $parentPHID; protected $viewPolicy; + protected $mailKey; private $content; private $rawContent; @@ -32,6 +36,13 @@ final class PhabricatorPaste extends PhabricatorPasteDAO PhabricatorPastePHIDTypePaste::TYPECONST); } + public function save() { + if (!$this->getMailKey()) { + $this->setMailKey(Filesystem::readRandomCharacters(20)); + } + return parent::save(); + } + public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, @@ -82,6 +93,14 @@ final class PhabricatorPaste extends PhabricatorPasteDAO return $this; } +/* -( PhabricatorSubscribableInterface Implementation )-------------------- */ + + + public function isAutomaticallySubscribed($phid) { + return ($this->authorPHID == $phid); + } + + /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ public function getUsersToNotifyOfTokenGiven() { diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index dc38ef7443..2b7e12d301 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -1507,6 +1507,18 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList { 'type' => 'php', 'name' => $this->getPatchPath('20130801.pastexactions.php'), ), + '20130805.pastemailkey.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20130805.pastemailkey.sql'), + ), + '20130805.pasteedges.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20130805.pasteedges.sql'), + ), + '20130805.pastemailkeypop.php' => array( + 'type' => 'php', + 'name' => $this->getPatchPath('20130805.pastemailkeypop.php'), + ), ); } }