1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-29 18:22:41 +01:00

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
This commit is contained in:
Bob Trahan 2013-08-05 17:11:46 -07:00
parent 42af0d66d9
commit 0e6b5073cd
11 changed files with 242 additions and 8 deletions

View file

@ -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;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_pastebin.pastebin_paste
ADD COLUMN `mailKey` varchar(20) NOT NULL;

View file

@ -0,0 +1,27 @@
<?php
echo "Populating pastes with mail keys...\n";
$table = new PhabricatorPaste();
$table->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";

View file

@ -759,6 +759,8 @@ phutil_register_library_map(array(
'PackageModifyMail' => 'applications/owners/mail/PackageModifyMail.php', 'PackageModifyMail' => 'applications/owners/mail/PackageModifyMail.php',
'PasteCreateMailReceiver' => 'applications/paste/mail/PasteCreateMailReceiver.php', 'PasteCreateMailReceiver' => 'applications/paste/mail/PasteCreateMailReceiver.php',
'PasteEmbedView' => 'applications/paste/view/PasteEmbedView.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', 'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php',
'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php', 'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php',
'PhabricatorAccessLog' => 'infrastructure/PhabricatorAccessLog.php', 'PhabricatorAccessLog' => 'infrastructure/PhabricatorAccessLog.php',
@ -2773,6 +2775,8 @@ phutil_register_library_map(array(
'PackageModifyMail' => 'PackageMail', 'PackageModifyMail' => 'PackageMail',
'PasteCreateMailReceiver' => 'PhabricatorMailReceiver', 'PasteCreateMailReceiver' => 'PhabricatorMailReceiver',
'PasteEmbedView' => 'AphrontView', 'PasteEmbedView' => 'AphrontView',
'PasteMockMailReceiver' => 'PhabricatorObjectMailReceiver',
'PasteReplyHandler' => 'PhabricatorMailReplyHandler',
'Phabricator404Controller' => 'PhabricatorController', 'Phabricator404Controller' => 'PhabricatorController',
'PhabricatorAWSConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorAWSConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions',
@ -3417,8 +3421,9 @@ phutil_register_library_map(array(
'PhabricatorPaste' => 'PhabricatorPaste' =>
array( array(
0 => 'PhabricatorPasteDAO', 0 => 'PhabricatorPasteDAO',
1 => 'PhabricatorTokenReceiverInterface', 1 => 'PhabricatorSubscribableInterface',
2 => 'PhabricatorPolicyInterface', 2 => 'PhabricatorTokenReceiverInterface',
3 => 'PhabricatorPolicyInterface',
), ),
'PhabricatorPasteCommentController' => 'PhabricatorPasteController', 'PhabricatorPasteCommentController' => 'PhabricatorPasteController',
'PhabricatorPasteConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPasteConfigOptions' => 'PhabricatorApplicationConfigOptions',

View file

@ -20,7 +20,12 @@ final class PhabricatorPasteConfigOptions
'metamta.paste.public-create-email', 'metamta.paste.public-create-email',
'string', 'string',
null) 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.'))
); );
} }

View file

@ -101,7 +101,11 @@ final class PhabricatorPasteEditor
} }
protected function supportsMail() { protected function supportsMail() {
return false; return true;
}
protected function getMailSubjectPrefix() {
return PhabricatorEnv::getEnvConfig('metamta.paste.subject-prefix');
} }
protected function getMailTo(PhabricatorLiskDAO $object) { protected function getMailTo(PhabricatorLiskDAO $object) {
@ -111,8 +115,18 @@ final class PhabricatorPasteEditor
); );
} }
protected function getMailCC(PhabricatorLiskDAO $object) { protected function buildReplyHandler(PhabricatorLiskDAO $object) {
return array(); 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() { protected function supportsFeed() {

View file

@ -66,6 +66,8 @@ final class PasteCreateMailReceiver
$mail->setRelatedPHID($paste->getPHID()); $mail->setRelatedPHID($paste->getPHID());
$subject_prefix =
PhabricatorEnv::getEnvConfig('metamta.paste.subject-prefix');
$subject = pht('You successfully created a paste.'); $subject = pht('You successfully created a paste.');
$paste_uri = PhabricatorEnv::getProductionURI($paste->getURI()); $paste_uri = PhabricatorEnv::getProductionURI($paste->getURI());
$body = new PhabricatorMetaMTAMailBody(); $body = new PhabricatorMetaMTAMailBody();
@ -74,7 +76,8 @@ final class PasteCreateMailReceiver
id(new PhabricatorMetaMTAMail()) id(new PhabricatorMetaMTAMail())
->addTos(array($sender->getPHID())) ->addTos(array($sender->getPHID()))
->setSubject('[Paste] '.$subject) ->setSubject($subject)
->setSubjectPrefix($subject_prefix)
->setFrom($sender->getPHID()) ->setFrom($sender->getPHID())
->setRelatedPHID($paste->getPHID()) ->setRelatedPHID($paste->getPHID())
->setBody($body->render()) ->setBody($body->render())

View file

@ -0,0 +1,40 @@
<?php
/**
* @group paste
*/
final class PasteMockMailReceiver extends PhabricatorObjectMailReceiver {
public function isEnabled() {
$app_class = 'PhabricatorApplicationPaste';
return PhabricatorApplication::isClassInstalled($app_class);
}
protected function getObjectPattern() {
return 'P[1-9]\d*';
}
protected function loadObject($pattern, PhabricatorUser $viewer) {
$id = (int)trim($pattern, 'P');
return id(new PhabricatorPasteQuery())
->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);
}
}

View file

@ -0,0 +1,92 @@
<?php
/**
* @group paste
*/
final class PasteReplyHandler extends PhabricatorMailReplyHandler {
public function validateMailReceiver($mail_receiver) {
if (!($mail_receiver instanceof PhabricatorPaste)) {
throw new Exception('Mail receiver is not a PhabricatorPaste.');
}
}
public function getPrivateReplyHandlerEmailAddress(
PhabricatorObjectHandle $handle) {
return $this->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();
}
}

View file

@ -4,7 +4,10 @@
* @group paste * @group paste
*/ */
final class PhabricatorPaste extends PhabricatorPasteDAO final class PhabricatorPaste extends PhabricatorPasteDAO
implements PhabricatorTokenReceiverInterface, PhabricatorPolicyInterface { implements
PhabricatorSubscribableInterface,
PhabricatorTokenReceiverInterface,
PhabricatorPolicyInterface {
protected $phid; protected $phid;
protected $title; protected $title;
@ -13,6 +16,7 @@ final class PhabricatorPaste extends PhabricatorPasteDAO
protected $language; protected $language;
protected $parentPHID; protected $parentPHID;
protected $viewPolicy; protected $viewPolicy;
protected $mailKey;
private $content; private $content;
private $rawContent; private $rawContent;
@ -32,6 +36,13 @@ final class PhabricatorPaste extends PhabricatorPasteDAO
PhabricatorPastePHIDTypePaste::TYPECONST); PhabricatorPastePHIDTypePaste::TYPECONST);
} }
public function save() {
if (!$this->getMailKey()) {
$this->setMailKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
}
public function getCapabilities() { public function getCapabilities() {
return array( return array(
PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_VIEW,
@ -82,6 +93,14 @@ final class PhabricatorPaste extends PhabricatorPasteDAO
return $this; return $this;
} }
/* -( PhabricatorSubscribableInterface Implementation )-------------------- */
public function isAutomaticallySubscribed($phid) {
return ($this->authorPHID == $phid);
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */ /* -( PhabricatorTokenReceiverInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() { public function getUsersToNotifyOfTokenGiven() {

View file

@ -1507,6 +1507,18 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'php', 'type' => 'php',
'name' => $this->getPatchPath('20130801.pastexactions.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'),
),
); );
} }
} }