@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

File - add transactions and editor

Summary: this ends up being a little weird since you can't actually edit files. Also, since we create files all sorts of ways, sometimes without even having a user, we don't bother logging transactions for those events. Fixes T3651. Turns out this work is important for T3612, which is a priority of mine to help get Pholio out the door.

Test Plan: left a comment on a file. it worked! use bin/mail to verify mail content looked correct.

Reviewers: epriestley

Reviewed By: epriestley

CC: Korvin, aran, wez

Maniphest Tasks: T3651, T3612

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

+585 -17
+32
resources/sql/patches/20130820.file-mailkey-populate.php
··· 1 + <?php 2 + 3 + echo "Populating Phabricator files with mail keys xactions...\n"; 4 + 5 + $table = new PhabricatorFile(); 6 + $table_name = $table->getTableName(); 7 + 8 + $conn_w = $table->establishConnection('w'); 9 + $conn_w->openTransaction(); 10 + 11 + $sql = array(); 12 + foreach (new LiskRawMigrationIterator($conn_w, 'file') as $row) { 13 + $sql[] = qsprintf( 14 + $conn_w, 15 + '(%d, %s)', 16 + $row['id'], 17 + Filesystem::readRandomCharacters(20)); 18 + } 19 + 20 + if ($sql) { 21 + foreach (PhabricatorLiskDAO::chunkSQL($sql, ', ') as $chunk) { 22 + queryfx( 23 + $conn_w, 24 + 'INSERT INTO %T (id, mailKey) VALUES %Q '. 25 + 'ON DUPLICATE KEY UPDATE mailKey = VALUES(mailKey)', 26 + $table_name, 27 + $chunk); 28 + } 29 + } 30 + 31 + $table->saveTransaction(); 32 + echo "Done.\n";
+2
resources/sql/patches/20130820.filemailkey.sql
··· 1 + ALTER TABLE {$NAMESPACE}_file.file 2 + ADD `mailKey` varchar(20) NOT NULL;
+42
resources/sql/patches/20130820.filexactions.sql
··· 1 + CREATE TABLE {$NAMESPACE}_file.file_transaction ( 2 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 3 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 5 + objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 6 + viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 7 + editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 8 + commentPHID VARCHAR(64) COLLATE utf8_bin, 9 + commentVersion INT UNSIGNED NOT NULL, 10 + transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin, 11 + oldValue LONGTEXT NOT NULL COLLATE utf8_bin, 12 + newValue LONGTEXT NOT NULL COLLATE utf8_bin, 13 + contentSource LONGTEXT NOT NULL COLLATE utf8_bin, 14 + metadata LONGTEXT NOT NULL COLLATE utf8_bin, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + 18 + UNIQUE KEY `key_phid` (phid), 19 + KEY `key_object` (objectPHID) 20 + 21 + ) ENGINE=InnoDB, COLLATE utf8_general_ci; 22 + 23 + CREATE TABLE {$NAMESPACE}_file.file_transaction_comment ( 24 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 25 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 26 + transactionPHID VARCHAR(64) COLLATE utf8_bin, 27 + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 28 + viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 29 + editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 30 + commentVersion INT UNSIGNED NOT NULL, 31 + content LONGTEXT NOT NULL COLLATE utf8_bin, 32 + contentSource LONGTEXT NOT NULL COLLATE utf8_bin, 33 + isDeleted BOOL NOT NULL, 34 + dateCreated INT UNSIGNED NOT NULL, 35 + dateModified INT UNSIGNED NOT NULL, 36 + 37 + UNIQUE KEY `key_phid` (phid), 38 + UNIQUE KEY `key_version` (transactionPHID, commentVersion), 39 + UNIQUE KEY `key_draft` (authorPHID, transactionPHID) 40 + 41 + ) ENGINE=InnoDB, COLLATE utf8_general_ci; 42 +
+19 -3
src/__phutil_library_map__.php
··· 596 596 'FeedPublisherHTTPWorker' => 'applications/feed/worker/FeedPublisherHTTPWorker.php', 597 597 'FeedPublisherWorker' => 'applications/feed/worker/FeedPublisherWorker.php', 598 598 'FeedPushWorker' => 'applications/feed/worker/FeedPushWorker.php', 599 - 'FilesCreateMailReceiver' => 'applications/files/mail/FilesCreateMailReceiver.php', 599 + 'FileCreateMailReceiver' => 'applications/files/mail/FileCreateMailReceiver.php', 600 + 'FileMailReceiver' => 'applications/files/mail/FileMailReceiver.php', 601 + 'FileReplyHandler' => 'applications/files/mail/FileReplyHandler.php', 600 602 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', 601 603 'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php', 602 604 'HarbormasterRunnerWorker' => 'applications/harbormaster/worker/HarbormasterRunnerWorker.php', ··· 1161 1163 'PhabricatorFeedStoryStatus' => 'applications/feed/story/PhabricatorFeedStoryStatus.php', 1162 1164 'PhabricatorFeedStoryTypeConstants' => 'applications/feed/constants/PhabricatorFeedStoryTypeConstants.php', 1163 1165 'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php', 1166 + 'PhabricatorFileCommentController' => 'applications/files/controller/PhabricatorFileCommentController.php', 1164 1167 'PhabricatorFileController' => 'applications/files/controller/PhabricatorFileController.php', 1165 1168 'PhabricatorFileDAO' => 'applications/files/storage/PhabricatorFileDAO.php', 1166 1169 'PhabricatorFileDataController' => 'applications/files/controller/PhabricatorFileDataController.php', 1167 1170 'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php', 1168 1171 'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php', 1172 + 'PhabricatorFileEditor' => 'applications/files/editor/PhabricatorFileEditor.php', 1169 1173 'PhabricatorFileImageMacro' => 'applications/macro/storage/PhabricatorFileImageMacro.php', 1170 1174 'PhabricatorFileInfoController' => 'applications/files/controller/PhabricatorFileInfoController.php', 1171 1175 'PhabricatorFileLinkListView' => 'view/layout/PhabricatorFileLinkListView.php', ··· 1181 1185 'PhabricatorFileStorageEngineSelector' => 'applications/files/engineselector/PhabricatorFileStorageEngineSelector.php', 1182 1186 'PhabricatorFileTestCase' => 'applications/files/storage/__tests__/PhabricatorFileTestCase.php', 1183 1187 'PhabricatorFileTestDataGenerator' => 'applications/files/lipsum/PhabricatorFileTestDataGenerator.php', 1188 + 'PhabricatorFileTransaction' => 'applications/files/storage/PhabricatorFileTransaction.php', 1189 + 'PhabricatorFileTransactionComment' => 'applications/files/storage/PhabricatorFileTransactionComment.php', 1190 + 'PhabricatorFileTransactionQuery' => 'applications/files/query/PhabricatorFileTransactionQuery.php', 1184 1191 'PhabricatorFileTransformController' => 'applications/files/controller/PhabricatorFileTransformController.php', 1185 1192 'PhabricatorFileUploadController' => 'applications/files/controller/PhabricatorFileUploadController.php', 1186 1193 'PhabricatorFileUploadDialogController' => 'applications/files/controller/PhabricatorFileUploadDialogController.php', ··· 2631 2638 'FeedPublisherHTTPWorker' => 'FeedPushWorker', 2632 2639 'FeedPublisherWorker' => 'FeedPushWorker', 2633 2640 'FeedPushWorker' => 'PhabricatorWorker', 2634 - 'FilesCreateMailReceiver' => 'PhabricatorMailReceiver', 2641 + 'FileCreateMailReceiver' => 'PhabricatorMailReceiver', 2642 + 'FileMailReceiver' => 'PhabricatorObjectMailReceiver', 2643 + 'FileReplyHandler' => 'PhabricatorMailReplyHandler', 2635 2644 'HarbormasterDAO' => 'PhabricatorLiskDAO', 2636 2645 'HarbormasterObject' => 'HarbormasterDAO', 2637 2646 'HarbormasterRunnerWorker' => 'PhabricatorWorker', ··· 3245 3254 'PhabricatorFile' => 3246 3255 array( 3247 3256 0 => 'PhabricatorFileDAO', 3248 - 1 => 'PhabricatorPolicyInterface', 3257 + 1 => 'PhabricatorTokenReceiverInterface', 3258 + 2 => 'PhabricatorSubscribableInterface', 3259 + 3 => 'PhabricatorPolicyInterface', 3249 3260 ), 3261 + 'PhabricatorFileCommentController' => 'PhabricatorFileController', 3250 3262 'PhabricatorFileController' => 'PhabricatorController', 3251 3263 'PhabricatorFileDAO' => 'PhabricatorLiskDAO', 3252 3264 'PhabricatorFileDataController' => 'PhabricatorFileController', 3253 3265 'PhabricatorFileDeleteController' => 'PhabricatorFileController', 3254 3266 'PhabricatorFileDropUploadController' => 'PhabricatorFileController', 3267 + 'PhabricatorFileEditor' => 'PhabricatorApplicationTransactionEditor', 3255 3268 'PhabricatorFileImageMacro' => 3256 3269 array( 3257 3270 0 => 'PhabricatorFileDAO', ··· 3275 3288 'PhabricatorFileStorageConfigurationException' => 'Exception', 3276 3289 'PhabricatorFileTestCase' => 'PhabricatorTestCase', 3277 3290 'PhabricatorFileTestDataGenerator' => 'PhabricatorTestDataGenerator', 3291 + 'PhabricatorFileTransaction' => 'PhabricatorApplicationTransaction', 3292 + 'PhabricatorFileTransactionComment' => 'PhabricatorApplicationTransactionComment', 3293 + 'PhabricatorFileTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3278 3294 'PhabricatorFileTransformController' => 'PhabricatorFileController', 3279 3295 'PhabricatorFileUploadController' => 'PhabricatorFileController', 3280 3296 'PhabricatorFileUploadDialogController' => 'PhabricatorFileController',
+4
src/applications/files/application/PhabricatorApplicationFiles.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group file 5 + */ 3 6 final class PhabricatorApplicationFiles extends PhabricatorApplication { 4 7 5 8 public function getBaseURI() { ··· 48 51 '(query/(?P<key>[^/]+)/)?' => 'PhabricatorFileListController', 49 52 'upload/' => 'PhabricatorFileUploadController', 50 53 'dropupload/' => 'PhabricatorFileDropUploadController', 54 + 'comment/(?P<id>[1-9]\d*)/' => 'PhabricatorFileCommentController', 51 55 'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorFileDeleteController', 52 56 'info/(?P<phid>[^/]+)/' => 'PhabricatorFileInfoController', 53 57 'data/(?P<key>[^/]+)/(?P<phid>[^/]+)/.*'
+3
src/applications/files/conduit/ConduitAPI_file_Method.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group conduit 5 + */ 3 6 abstract class ConduitAPI_file_Method extends ConduitAPIMethod { 4 7 5 8 public function getApplication() {
+9 -1
src/applications/files/config/PhabricatorFilesConfigOptions.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group file 5 + */ 3 6 final class PhabricatorFilesConfigOptions 4 7 extends PhabricatorApplicationConfigOptions { 5 8 ··· 158 161 'metamta.files.public-create-email', 159 162 'string', 160 163 null) 161 - ->setDescription(pht('Allow uploaded files via email.')), 164 + ->setDescription(pht('Allow uploaded files via email.')), 165 + $this->newOption( 166 + 'metamta.files.subject-prefix', 167 + 'string', 168 + '[File]') 169 + ->setDescription(pht('Subject prefix for paste email.')), 162 170 $this->newOption('files.enable-imagemagick', 'bool', false) 163 171 ->setBoolOptions( 164 172 array(
+73
src/applications/files/controller/PhabricatorFileCommentController.php
··· 1 + <?php 2 + 3 + /** 4 + * @group file 5 + */ 6 + final class PhabricatorFileCommentController 7 + extends PhabricatorFileController { 8 + 9 + private $id; 10 + 11 + public function willProcessRequest(array $data) { 12 + $this->id = idx($data, 'id'); 13 + } 14 + 15 + public function processRequest() { 16 + $request = $this->getRequest(); 17 + $user = $request->getUser(); 18 + 19 + if (!$request->isFormPost()) { 20 + return new Aphront400Response(); 21 + } 22 + 23 + $file = id(new PhabricatorFileQuery()) 24 + ->setViewer($user) 25 + ->withIDs(array($this->id)) 26 + ->executeOne(); 27 + if (!$file) { 28 + return new Aphront404Response(); 29 + } 30 + 31 + $is_preview = $request->isPreviewRequest(); 32 + $draft = PhabricatorDraft::buildFromRequest($request); 33 + 34 + $view_uri = $file->getInfoURI(); 35 + 36 + $xactions = array(); 37 + $xactions[] = id(new PhabricatorFileTransaction()) 38 + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) 39 + ->attachComment( 40 + id(new PhabricatorFileTransactionComment()) 41 + ->setContent($request->getStr('comment'))); 42 + 43 + $editor = id(new PhabricatorFileEditor()) 44 + ->setActor($user) 45 + ->setContinueOnNoEffect($request->isContinueRequest()) 46 + ->setContentSourceFromRequest($request) 47 + ->setIsPreview($is_preview); 48 + 49 + try { 50 + $xactions = $editor->applyTransactions($file, $xactions); 51 + } catch (PhabricatorApplicationTransactionNoEffectException $ex) { 52 + return id(new PhabricatorApplicationTransactionNoEffectResponse()) 53 + ->setCancelURI($view_uri) 54 + ->setException($ex); 55 + } 56 + 57 + if ($draft) { 58 + $draft->replaceOrDelete(); 59 + } 60 + 61 + if ($request->isAjax()) { 62 + return id(new PhabricatorApplicationTransactionResponse()) 63 + ->setViewer($user) 64 + ->setTransactions($xactions) 65 + ->setIsPreview($is_preview) 66 + ->setAnchorOffset($request->getStr('anchor')); 67 + } else { 68 + return id(new AphrontRedirectResponse()) 69 + ->setURI($view_uri); 70 + } 71 + } 72 + 73 + }
+58 -2
src/applications/files/controller/PhabricatorFileInfoController.php
··· 21 21 return new Aphront404Response(); 22 22 } 23 23 24 + $phid = $file->getPHID(); 25 + $xactions = id(new PhabricatorFileTransactionQuery()) 26 + ->setViewer($user) 27 + ->withObjectPHIDs(array($phid)) 28 + ->execute(); 29 + 24 30 $this->loadHandles(array($file->getAuthorPHID())); 25 - $phid = $file->getPHID(); 26 31 $header = id(new PhabricatorHeaderView()) 27 32 ->setHeader($file->getName()); 28 33 ··· 36 41 37 42 $actions = $this->buildActionView($file); 38 43 $properties = $this->buildPropertyView($file); 39 - 44 + $timeline = $this->buildTransactionView($file, $xactions); 40 45 $crumbs = $this->buildApplicationCrumbs(); 41 46 $crumbs->setActionList($actions); 42 47 $crumbs->addCrumb( ··· 50 55 $header, 51 56 $actions, 52 57 $properties, 58 + $timeline 53 59 ), 54 60 array( 55 61 'title' => $file->getName(), 56 62 'device' => true, 63 + 'pageObjects' => array($file->getPHID()), 57 64 )); 65 + } 66 + 67 + private function buildTransactionView( 68 + PhabricatorFile $file, 69 + array $xactions) { 70 + 71 + $user = $this->getRequest()->getUser(); 72 + $engine = id(new PhabricatorMarkupEngine()) 73 + ->setViewer($user); 74 + foreach ($xactions as $xaction) { 75 + if ($xaction->getComment()) { 76 + $engine->addObject( 77 + $xaction->getComment(), 78 + PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); 79 + } 80 + } 81 + $engine->process(); 82 + 83 + $timeline = id(new PhabricatorApplicationTransactionView()) 84 + ->setUser($user) 85 + ->setObjectPHID($file->getPHID()) 86 + ->setTransactions($xactions) 87 + ->setMarkupEngine($engine); 88 + 89 + $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); 90 + 91 + $add_comment_header = id(new PhabricatorHeaderView()) 92 + ->setHeader( 93 + $is_serious 94 + ? pht('Add Comment') 95 + : pht('Question File Integrity')); 96 + 97 + $submit_button_name = $is_serious 98 + ? pht('Add Comment') 99 + : pht('Debate the Bits'); 100 + 101 + $draft = PhabricatorDraft::newFromUserAndKey($user, $file->getPHID()); 102 + 103 + $add_comment_form = id(new PhabricatorApplicationTransactionCommentView()) 104 + ->setUser($user) 105 + ->setObjectPHID($file->getPHID()) 106 + ->setDraft($draft) 107 + ->setAction($this->getApplicationURI('/comment/'.$file->getID().'/')) 108 + ->setSubmitButtonName($submit_button_name); 109 + 110 + return array( 111 + $timeline, 112 + $add_comment_header, 113 + $add_comment_form); 58 114 } 59 115 60 116 private function buildActionView(PhabricatorFile $file) {
+89
src/applications/files/editor/PhabricatorFileEditor.php
··· 1 + <?php 2 + 3 + /** 4 + * @group file 5 + */ 6 + final class PhabricatorFileEditor 7 + extends PhabricatorApplicationTransactionEditor { 8 + 9 + public function getTransactionTypes() { 10 + $types = parent::getTransactionTypes(); 11 + 12 + $types[] = PhabricatorTransactions::TYPE_COMMENT; 13 + 14 + return $types; 15 + } 16 + 17 + protected function getCustomTransactionOldValue( 18 + PhabricatorLiskDAO $object, 19 + PhabricatorApplicationTransaction $xaction) { 20 + 21 + } 22 + 23 + protected function getCustomTransactionNewValue( 24 + PhabricatorLiskDAO $object, 25 + PhabricatorApplicationTransaction $xaction) { 26 + 27 + } 28 + 29 + protected function applyCustomInternalTransaction( 30 + PhabricatorLiskDAO $object, 31 + PhabricatorApplicationTransaction $xaction) { 32 + } 33 + 34 + protected function applyCustomExternalTransaction( 35 + PhabricatorLiskDAO $object, 36 + PhabricatorApplicationTransaction $xaction) { 37 + } 38 + 39 + protected function supportsMail() { 40 + return true; 41 + } 42 + 43 + protected function getMailSubjectPrefix() { 44 + return PhabricatorEnv::getEnvConfig('metamta.files.subject-prefix'); 45 + } 46 + 47 + protected function getMailTo(PhabricatorLiskDAO $object) { 48 + return array( 49 + $object->getAuthorPHID(), 50 + $this->requireActor()->getPHID(), 51 + ); 52 + } 53 + 54 + protected function buildReplyHandler(PhabricatorLiskDAO $object) { 55 + return id(new FileReplyHandler()) 56 + ->setMailReceiver($object); 57 + } 58 + 59 + protected function buildMailTemplate(PhabricatorLiskDAO $object) { 60 + $id = $object->getID(); 61 + $name = $object->getName(); 62 + 63 + return id(new PhabricatorMetaMTAMail()) 64 + ->setSubject("F{$id}: {$name}") 65 + ->addHeader('Thread-Topic', "F{$id}"); 66 + } 67 + 68 + protected function buildMailBody( 69 + PhabricatorLiskDAO $object, 70 + array $xactions) { 71 + 72 + $body = parent::buildMailBody($object, $xactions); 73 + 74 + $body->addTextSection( 75 + pht('FILE DETAIL'), 76 + PhabricatorEnv::getProductionURI($object->getInfoURI())); 77 + 78 + return $body; 79 + } 80 + 81 + protected function supportsFeed() { 82 + return true; 83 + } 84 + 85 + protected function supportsSearch() { 86 + return false; 87 + } 88 + 89 + }
+40
src/applications/files/mail/FileMailReceiver.php
··· 1 + <?php 2 + 3 + /** 4 + * @group file 5 + */ 6 + final class FileMailReceiver extends PhabricatorObjectMailReceiver { 7 + 8 + public function isEnabled() { 9 + $app_class = 'PhabricatorApplicationFiles'; 10 + return PhabricatorApplication::isClassInstalled($app_class); 11 + } 12 + 13 + protected function getObjectPattern() { 14 + return 'F[1-9]\d*'; 15 + } 16 + 17 + protected function loadObject($pattern, PhabricatorUser $viewer) { 18 + $id = (int)trim($pattern, 'F'); 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 FileReplyHandler()) 32 + ->setMailReceiver($object); 33 + 34 + $handler->setActor($sender); 35 + $handler->setExcludeMailRecipientPHIDs( 36 + $mail->loadExcludeMailRecipientPHIDs()); 37 + $handler->processEmail($mail); 38 + } 39 + 40 + }
+92
src/applications/files/mail/FileReplyHandler.php
··· 1 + <?php 2 + 3 + /** 4 + * @group file 5 + */ 6 + final class FileReplyHandler extends PhabricatorMailReplyHandler { 7 + 8 + public function validateMailReceiver($mail_receiver) { 9 + if (!($mail_receiver instanceof PhabricatorFile)) { 10 + throw new Exception('Mail receiver is not a PhabricatorFile.'); 11 + } 12 + } 13 + 14 + public function getPrivateReplyHandlerEmailAddress( 15 + PhabricatorObjectHandle $handle) { 16 + return $this->getDefaultPrivateReplyHandlerEmailAddress($handle, 'F'); 17 + } 18 + 19 + public function getPublicReplyHandlerEmailAddress() { 20 + return $this->getDefaultPublicReplyHandlerEmailAddress('F'); 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 + $file = $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 PhabricatorFileTransaction()) 62 + ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) 63 + ->setNewValue(array('-' => array($actor->getPHID()))); 64 + $xactions[] = $xaction; 65 + break; 66 + } 67 + 68 + $xactions[] = id(new PhabricatorFileTransaction()) 69 + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) 70 + ->attachComment( 71 + id(new PhabricatorFileTransactionComment()) 72 + ->setContent($body)); 73 + 74 + $editor = id(new PhabricatorFileEditor()) 75 + ->setActor($actor) 76 + ->setContentSource($content_source) 77 + ->setContinueOnNoEffect(true) 78 + ->setIsPreview(false); 79 + 80 + try { 81 + $xactions = $editor->applyTransactions($file, $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 + }
+5 -2
src/applications/files/mail/FilesCreateMailReceiver.php src/applications/files/mail/FileCreateMailReceiver.php
··· 3 3 /** 4 4 * @group files 5 5 */ 6 - final class FilesCreateMailReceiver 6 + final class FileCreateMailReceiver 7 7 extends PhabricatorMailReceiver { 8 8 9 9 public function isEnabled() { ··· 48 48 } else { 49 49 $subject = pht('You successfully uploaded a file.'); 50 50 } 51 + $subject_prefix = 52 + PhabricatorEnv::getEnvConfig('metamta.files.subject-prefix'); 51 53 52 54 $file_uris = array(); 53 55 foreach ($attachment_phids as $phid) { ··· 61 63 62 64 id(new PhabricatorMetaMTAMail()) 63 65 ->addTos(array($sender->getPHID())) 64 - ->setSubject('[Files] '.$subject) 66 + ->setSubject($subject) 67 + ->setSubjectPrefix($subject_prefix) 65 68 ->setFrom($sender->getPHID()) 66 69 ->setRelatedPHID($first_phid) 67 70 ->setBody($body->render())
+3
src/applications/files/query/PhabricatorFileQuery.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group file 5 + */ 3 6 final class PhabricatorFileQuery 4 7 extends PhabricatorCursorPagedPolicyAwareQuery { 5 8
+3
src/applications/files/query/PhabricatorFileSearchEngine.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group file 5 + */ 3 6 final class PhabricatorFileSearchEngine 4 7 extends PhabricatorApplicationSearchEngine { 5 8
+13
src/applications/files/query/PhabricatorFileTransactionQuery.php
··· 1 + <?php 2 + 3 + /** 4 + * @group file 5 + */ 6 + final class PhabricatorFileTransactionQuery 7 + extends PhabricatorApplicationTransactionQuery { 8 + 9 + public function getTemplateApplicationTransaction() { 10 + return new PhabricatorFileTransaction(); 11 + } 12 + 13 + }
+37 -8
src/applications/files/storage/PhabricatorFile.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group file 5 + */ 3 6 final class PhabricatorFile extends PhabricatorFileDAO 4 - implements PhabricatorPolicyInterface { 7 + implements 8 + PhabricatorTokenReceiverInterface, 9 + PhabricatorSubscribableInterface, 10 + PhabricatorPolicyInterface { 5 11 6 12 const STORAGE_FORMAT_RAW = 'raw'; 7 13 ··· 16 22 protected $secretKey; 17 23 protected $contentHash; 18 24 protected $metadata = array(); 25 + protected $mailKey; 19 26 20 27 protected $storageEngine; 21 28 protected $storageFormat; ··· 36 43 public function generatePHID() { 37 44 return PhabricatorPHID::generateNewPHID( 38 45 PhabricatorFilePHIDTypeFile::TYPECONST); 46 + } 47 + 48 + public function save() { 49 + if (!$this->getSecretKey()) { 50 + $this->setSecretKey($this->generateSecretKey()); 51 + } 52 + if (!$this->getMailKey()) { 53 + $this->setMailKey(Filesystem::readRandomCharacters(20)); 54 + } 55 + return parent::save(); 39 56 } 40 57 41 58 public static function readUploadedFileData($spec) { ··· 648 665 return ($key == $this->getSecretKey()); 649 666 } 650 667 651 - public function save() { 652 - if (!$this->getSecretKey()) { 653 - $this->setSecretKey($this->generateSecretKey()); 654 - } 655 - return parent::save(); 656 - } 657 - 658 668 public function generateSecretKey() { 659 669 return Filesystem::readRandomCharacters(20); 660 670 } ··· 819 829 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 820 830 return false; 821 831 } 832 + 833 + 834 + /* -( PhabricatorSubscribableInterface Implementation )-------------------- */ 835 + 836 + 837 + public function isAutomaticallySubscribed($phid) { 838 + return ($this->authorPHID == $phid); 839 + } 840 + 841 + 842 + /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ 843 + 844 + 845 + public function getUsersToNotifyOfTokenGiven() { 846 + return array( 847 + $this->getAuthorPHID(), 848 + ); 849 + } 850 + 822 851 823 852 }
+3
src/applications/files/storage/PhabricatorFileDAO.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group file 5 + */ 3 6 abstract class PhabricatorFileDAO extends PhabricatorLiskDAO { 4 7 5 8 public function getApplicationName() {
+1 -1
src/applications/files/storage/PhabricatorFileStorageBlob.php
··· 3 3 /** 4 4 * Simple blob store DAO for @{class:PhabricatorMySQLFileStorageEngine}. 5 5 * 6 - * @group filestorage 6 + * @group file 7 7 */ 8 8 final class PhabricatorFileStorageBlob extends PhabricatorFileDAO { 9 9 // max_allowed_packet defaults to 1 MiB, escaping can make the data twice
+21
src/applications/files/storage/PhabricatorFileTransaction.php
··· 1 + <?php 2 + 3 + /** 4 + * @group file 5 + */ 6 + final class PhabricatorFileTransaction 7 + extends PhabricatorApplicationTransaction { 8 + 9 + public function getApplicationName() { 10 + return 'file'; 11 + } 12 + 13 + public function getApplicationTransactionType() { 14 + return PhabricatorFilePHIDTypeFile::TYPECONST; 15 + } 16 + 17 + public function getApplicationTransactionCommentObject() { 18 + return new PhabricatorFileTransactionComment(); 19 + } 20 + 21 + }
+17
src/applications/files/storage/PhabricatorFileTransactionComment.php
··· 1 + <?php 2 + 3 + /** 4 + * @group file 5 + */ 6 + final class PhabricatorFileTransactionComment 7 + extends PhabricatorApplicationTransactionComment { 8 + 9 + public function getApplicationTransactionObject() { 10 + return new PhabricatorFileTransaction(); 11 + } 12 + 13 + public function shouldUseMarkupCache($field) { 14 + // Only cache submitted comments. 15 + return ($this->getTransactionPHID() != null); 16 + } 17 + }
+3
src/applications/files/storage/PhabricatorTransformedFile.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group file 5 + */ 3 6 final class PhabricatorTransformedFile extends PhabricatorFileDAO { 4 7 5 8 protected $originalPHID;
+3
src/applications/files/storage/__tests__/PhabricatorFileTestCase.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group file 5 + */ 3 6 final class PhabricatorFileTestCase extends PhabricatorTestCase { 4 7 5 8 public function getPhabricatorTestCaseConfiguration() {
+13
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1559 1559 'type' => 'sql', 1560 1560 'name' => $this->getPatchPath('20130826.divinernode.sql'), 1561 1561 ), 1562 + '20130820.filexactions.sql' => array( 1563 + 'type' => 'sql', 1564 + 'name' => $this->getPatchPath('20130820.filexactions.sql'), 1565 + ), 1566 + '20130820.filemailkey.sql' => array( 1567 + 'type' => 'sql', 1568 + 'name' => $this->getPatchPath('20130820.filemailkey.sql'), 1569 + ), 1570 + '20130820.file-mailkey-populate.php' => array( 1571 + 'type' => 'php', 1572 + 'name' => 1573 + $this->getPatchPath('20130820.file-mailkey-populate.php'), 1574 + ), 1562 1575 ); 1563 1576 } 1564 1577 }