@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Paste - add support for email replies and subscribers

Summary: Email replies and subscribers seem to go hand in hand so deploy both at once.

Test Plan: played around with bin/mail. Verified replies posted comments on the paste.

Reviewers: epriestley

Reviewed By: epriestley

CC: aran, Korvin

Maniphest Tasks: T3650

Differential Revision: https://secure.phabricator.com/D6682

+242 -8
+15
resources/sql/patches/20130805.pasteedges.sql
··· 1 + CREATE TABLE {$NAMESPACE}_pastebin.edge ( 2 + src VARCHAR(64) NOT NULL COLLATE utf8_bin, 3 + type VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + dst VARCHAR(64) NOT NULL COLLATE utf8_bin, 5 + dateCreated INT UNSIGNED NOT NULL, 6 + seq INT UNSIGNED NOT NULL, 7 + dataID INT UNSIGNED, 8 + PRIMARY KEY (src, type, dst), 9 + KEY (src, type, dateCreated, seq) 10 + ) ENGINE=InnoDB, COLLATE utf8_general_ci; 11 + 12 + CREATE TABLE {$NAMESPACE}_pastebin.edgedata ( 13 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 14 + data LONGTEXT NOT NULL COLLATE utf8_bin 15 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+2
resources/sql/patches/20130805.pastemailkey.sql
··· 1 + ALTER TABLE {$NAMESPACE}_pastebin.pastebin_paste 2 + ADD COLUMN `mailKey` varchar(20) NOT NULL;
+27
resources/sql/patches/20130805.pastemailkeypop.php
··· 1 + <?php 2 + 3 + echo "Populating pastes with mail keys...\n"; 4 + 5 + $table = new PhabricatorPaste(); 6 + $table->openTransaction(); 7 + $conn_w = $table->establishConnection('w'); 8 + 9 + foreach (new LiskMigrationIterator($table) as $paste) { 10 + $id = $paste->getID(); 11 + 12 + echo "P{$id}: "; 13 + if (!$paste->getMailKey()) { 14 + queryfx( 15 + $conn_w, 16 + 'UPDATE %T SET mailKey = %s WHERE id = %d', 17 + $paste->getTableName(), 18 + Filesystem::readRandomCharacters(20), 19 + $id); 20 + echo("Generated Key\n"); 21 + } else { 22 + echo "-\n"; 23 + } 24 + } 25 + 26 + $table->saveTransaction(); 27 + echo "Done.\n";
+7 -2
src/__phutil_library_map__.php
··· 759 759 'PackageModifyMail' => 'applications/owners/mail/PackageModifyMail.php', 760 760 'PasteCreateMailReceiver' => 'applications/paste/mail/PasteCreateMailReceiver.php', 761 761 'PasteEmbedView' => 'applications/paste/view/PasteEmbedView.php', 762 + 'PasteMockMailReceiver' => 'applications/paste/mail/PasteMockMailReceiver.php', 763 + 'PasteReplyHandler' => 'applications/paste/mail/PasteReplyHandler.php', 762 764 'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php', 763 765 'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php', 764 766 'PhabricatorAccessLog' => 'infrastructure/PhabricatorAccessLog.php', ··· 2773 2775 'PackageModifyMail' => 'PackageMail', 2774 2776 'PasteCreateMailReceiver' => 'PhabricatorMailReceiver', 2775 2777 'PasteEmbedView' => 'AphrontView', 2778 + 'PasteMockMailReceiver' => 'PhabricatorObjectMailReceiver', 2779 + 'PasteReplyHandler' => 'PhabricatorMailReplyHandler', 2776 2780 'Phabricator404Controller' => 'PhabricatorController', 2777 2781 'PhabricatorAWSConfigOptions' => 'PhabricatorApplicationConfigOptions', 2778 2782 'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions', ··· 3417 3421 'PhabricatorPaste' => 3418 3422 array( 3419 3423 0 => 'PhabricatorPasteDAO', 3420 - 1 => 'PhabricatorTokenReceiverInterface', 3421 - 2 => 'PhabricatorPolicyInterface', 3424 + 1 => 'PhabricatorSubscribableInterface', 3425 + 2 => 'PhabricatorTokenReceiverInterface', 3426 + 3 => 'PhabricatorPolicyInterface', 3422 3427 ), 3423 3428 'PhabricatorPasteCommentController' => 'PhabricatorPasteController', 3424 3429 'PhabricatorPasteConfigOptions' => 'PhabricatorApplicationConfigOptions',
+6 -1
src/applications/paste/config/PhabricatorPasteConfigOptions.php
··· 20 20 'metamta.paste.public-create-email', 21 21 'string', 22 22 null) 23 - ->setDescription(pht('Allow creating pastes via email.')) 23 + ->setDescription(pht('Allow creating pastes via email.')), 24 + $this->newOption( 25 + 'metamta.paste.subject-prefix', 26 + 'string', 27 + '[Paste]') 28 + ->setDescription(pht('Subject prefix for paste email.')) 24 29 ); 25 30 } 26 31
+17 -3
src/applications/paste/editor/PhabricatorPasteEditor.php
··· 101 101 } 102 102 103 103 protected function supportsMail() { 104 - return false; 104 + return true; 105 + } 106 + 107 + protected function getMailSubjectPrefix() { 108 + return PhabricatorEnv::getEnvConfig('metamta.paste.subject-prefix'); 105 109 } 106 110 107 111 protected function getMailTo(PhabricatorLiskDAO $object) { ··· 111 115 ); 112 116 } 113 117 114 - protected function getMailCC(PhabricatorLiskDAO $object) { 115 - return array(); 118 + protected function buildReplyHandler(PhabricatorLiskDAO $object) { 119 + return id(new PasteReplyHandler()) 120 + ->setMailReceiver($object); 121 + } 122 + 123 + protected function buildMailTemplate(PhabricatorLiskDAO $object) { 124 + $id = $object->getID(); 125 + $name = $object->getTitle(); 126 + 127 + return id(new PhabricatorMetaMTAMail()) 128 + ->setSubject("P{$id}: {$name}") 129 + ->addHeader('Thread-Topic', "P{$id}"); 116 130 } 117 131 118 132 protected function supportsFeed() {
+4 -1
src/applications/paste/mail/PasteCreateMailReceiver.php
··· 66 66 67 67 $mail->setRelatedPHID($paste->getPHID()); 68 68 69 + $subject_prefix = 70 + PhabricatorEnv::getEnvConfig('metamta.paste.subject-prefix'); 69 71 $subject = pht('You successfully created a paste.'); 70 72 $paste_uri = PhabricatorEnv::getProductionURI($paste->getURI()); 71 73 $body = new PhabricatorMetaMTAMailBody(); ··· 74 76 75 77 id(new PhabricatorMetaMTAMail()) 76 78 ->addTos(array($sender->getPHID())) 77 - ->setSubject('[Paste] '.$subject) 79 + ->setSubject($subject) 80 + ->setSubjectPrefix($subject_prefix) 78 81 ->setFrom($sender->getPHID()) 79 82 ->setRelatedPHID($paste->getPHID()) 80 83 ->setBody($body->render())
+40
src/applications/paste/mail/PasteMockMailReceiver.php
··· 1 + <?php 2 + 3 + /** 4 + * @group paste 5 + */ 6 + final class PasteMockMailReceiver extends PhabricatorObjectMailReceiver { 7 + 8 + public function isEnabled() { 9 + $app_class = 'PhabricatorApplicationPaste'; 10 + return PhabricatorApplication::isClassInstalled($app_class); 11 + } 12 + 13 + protected function getObjectPattern() { 14 + return 'P[1-9]\d*'; 15 + } 16 + 17 + protected function loadObject($pattern, PhabricatorUser $viewer) { 18 + $id = (int)trim($pattern, 'P'); 19 + 20 + return id(new PhabricatorPasteQuery()) 21 + ->setViewer($viewer) 22 + ->withIDs(array($id)) 23 + ->executeOne(); 24 + } 25 + 26 + protected function processReceivedObjectMail( 27 + PhabricatorMetaMTAReceivedMail $mail, 28 + PhabricatorLiskDAO $object, 29 + PhabricatorUser $sender) { 30 + 31 + $handler = id(new PasteReplyHandler()) 32 + ->setMailReceiver($object); 33 + 34 + $handler->setActor($sender); 35 + $handler->setExcludeMailRecipientPHIDs( 36 + $mail->loadExcludeMailRecipientPHIDs()); 37 + $handler->processEmail($mail); 38 + } 39 + 40 + }
+92
src/applications/paste/mail/PasteReplyHandler.php
··· 1 + <?php 2 + 3 + /** 4 + * @group paste 5 + */ 6 + final class PasteReplyHandler extends PhabricatorMailReplyHandler { 7 + 8 + public function validateMailReceiver($mail_receiver) { 9 + if (!($mail_receiver instanceof PhabricatorPaste)) { 10 + throw new Exception('Mail receiver is not a PhabricatorPaste.'); 11 + } 12 + } 13 + 14 + public function getPrivateReplyHandlerEmailAddress( 15 + PhabricatorObjectHandle $handle) { 16 + return $this->getDefaultPrivateReplyHandlerEmailAddress($handle, 'P'); 17 + } 18 + 19 + public function getPublicReplyHandlerEmailAddress() { 20 + return $this->getDefaultPublicReplyHandlerEmailAddress('P'); 21 + } 22 + 23 + public function getReplyHandlerInstructions() { 24 + if ($this->supportsReplies()) { 25 + return pht('Reply to comment or !unsubscribe.'); 26 + } else { 27 + return null; 28 + } 29 + } 30 + 31 + protected function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) { 32 + $actor = $this->getActor(); 33 + $paste = $this->getMailReceiver(); 34 + 35 + $body = $mail->getCleanTextBody(); 36 + $body = trim($body); 37 + $body = $this->enhanceBodyWithAttachments($body, $mail->getAttachments()); 38 + 39 + $content_source = PhabricatorContentSource::newForSource( 40 + PhabricatorContentSource::SOURCE_EMAIL, 41 + array( 42 + 'id' => $mail->getID(), 43 + )); 44 + 45 + $lines = explode("\n", trim($body)); 46 + $first_line = head($lines); 47 + 48 + $xactions = array(); 49 + $command = null; 50 + $matches = null; 51 + if (preg_match('/^!(\w+)/', $first_line, $matches)) { 52 + $lines = array_slice($lines, 1); 53 + $body = implode("\n", $lines); 54 + $body = trim($body); 55 + 56 + $command = $matches[1]; 57 + } 58 + 59 + switch ($command) { 60 + case 'unsubscribe': 61 + $xaction = id(new PhabricatorPasteTransaction()) 62 + ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) 63 + ->setNewValue(array('-' => array($actor->getPHID()))); 64 + $xactions[] = $xaction; 65 + break; 66 + } 67 + 68 + $xactions[] = id(new PhabricatorPasteTransaction()) 69 + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) 70 + ->attachComment( 71 + id(new PhabricatorPasteTransactionComment()) 72 + ->setContent($body)); 73 + 74 + $editor = id(new PhabricatorPasteEditor()) 75 + ->setActor($actor) 76 + ->setContentSource($content_source) 77 + ->setContinueOnNoEffect(true) 78 + ->setIsPreview(false); 79 + 80 + try { 81 + $xactions = $editor->applyTransactions($paste, $xactions); 82 + } catch (PhabricatorApplicationTransactionNoEffectException $ex) { 83 + // just do nothing, though unclear why you're sending a blank email 84 + return true; 85 + } 86 + 87 + $head_xaction = head($xactions); 88 + return $head_xaction->getID(); 89 + 90 + } 91 + 92 + }
+20 -1
src/applications/paste/storage/PhabricatorPaste.php
··· 4 4 * @group paste 5 5 */ 6 6 final class PhabricatorPaste extends PhabricatorPasteDAO 7 - implements PhabricatorTokenReceiverInterface, PhabricatorPolicyInterface { 7 + implements 8 + PhabricatorSubscribableInterface, 9 + PhabricatorTokenReceiverInterface, 10 + PhabricatorPolicyInterface { 8 11 9 12 protected $phid; 10 13 protected $title; ··· 13 16 protected $language; 14 17 protected $parentPHID; 15 18 protected $viewPolicy; 19 + protected $mailKey; 16 20 17 21 private $content; 18 22 private $rawContent; ··· 30 34 public function generatePHID() { 31 35 return PhabricatorPHID::generateNewPHID( 32 36 PhabricatorPastePHIDTypePaste::TYPECONST); 37 + } 38 + 39 + public function save() { 40 + if (!$this->getMailKey()) { 41 + $this->setMailKey(Filesystem::readRandomCharacters(20)); 42 + } 43 + return parent::save(); 33 44 } 34 45 35 46 public function getCapabilities() { ··· 81 92 $this->rawContent = $raw_content; 82 93 return $this; 83 94 } 95 + 96 + /* -( PhabricatorSubscribableInterface Implementation )-------------------- */ 97 + 98 + 99 + public function isAutomaticallySubscribed($phid) { 100 + return ($this->authorPHID == $phid); 101 + } 102 + 84 103 85 104 /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ 86 105
+12
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1507 1507 'type' => 'php', 1508 1508 'name' => $this->getPatchPath('20130801.pastexactions.php'), 1509 1509 ), 1510 + '20130805.pastemailkey.sql' => array( 1511 + 'type' => 'sql', 1512 + 'name' => $this->getPatchPath('20130805.pastemailkey.sql'), 1513 + ), 1514 + '20130805.pasteedges.sql' => array( 1515 + 'type' => 'sql', 1516 + 'name' => $this->getPatchPath('20130805.pasteedges.sql'), 1517 + ), 1518 + '20130805.pastemailkeypop.php' => array( 1519 + 'type' => 'php', 1520 + 'name' => $this->getPatchPath('20130805.pastemailkeypop.php'), 1521 + ), 1510 1522 ); 1511 1523 } 1512 1524 }