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

Genericize transactions in Pholio

Summary:
Split Pholio's transaction implementation into generic and application-specific parts. Moves us toward generic transactions, with support for:

- Editing and deleting comments.
- Setting visibility of individual comments (I'm not a fan of this feature but we'll see).

I want to move everything to a more generic piece of infrastructure but there's very little they can share right now so adding transactions to, e.g., Paste or Macros (T2157) means massive amounts of similar code.

Tons of work left to do here, but I think it basically works. Here's a screenshot:

{F26820}

Test Plan: Made transactions in Pholio.

Reviewers: btrahan, vrana, chad

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2104

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

+1374 -491
+51
resources/sql/patches/20121209.pholioxactions.sql
··· 1 + DROP TABLE {$NAMESPACE}_pholio.pholio_transaction; 2 + DROP TABLE {$NAMESPACE}_pholio.pholio_pixelcomment; 3 + 4 + CREATE TABLE {$NAMESPACE}_pholio.pholio_transaction ( 5 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 6 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 7 + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 8 + objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 9 + viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 10 + editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 11 + commentPHID VARCHAR(64) COLLATE utf8_bin, 12 + commentVersion INT UNSIGNED NOT NULL, 13 + transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin, 14 + oldValue LONGTEXT NOT NULL COLLATE utf8_bin, 15 + newValue LONGTEXT NOT NULL COLLATE utf8_bin, 16 + contentSource LONGTEXT NOT NULL COLLATE utf8_bin, 17 + dateCreated INT UNSIGNED NOT NULL, 18 + dateModified INT UNSIGNED NOT NULL, 19 + 20 + UNIQUE KEY `key_phid` (phid), 21 + KEY `key_object` (objectPHID) 22 + 23 + ) ENGINE=InnoDB, COLLATE utf8_general_ci; 24 + 25 + CREATE TABLE {$NAMESPACE}_pholio.pholio_transaction_comment ( 26 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 27 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 28 + transactionPHID VARCHAR(64) COLLATE utf8_bin, 29 + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 30 + viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 31 + editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 32 + commentVersion INT UNSIGNED NOT NULL, 33 + content LONGTEXT NOT NULL COLLATE utf8_bin, 34 + contentSource LONGTEXT NOT NULL COLLATE utf8_bin, 35 + isDeleted BOOL NOT NULL, 36 + dateCreated INT UNSIGNED NOT NULL, 37 + dateModified INT UNSIGNED NOT NULL, 38 + 39 + mockID INT UNSIGNED, 40 + imageID INT UNSIGNED, 41 + x INT UNSIGNED, 42 + y INT UNSIGNED, 43 + width INT UNSIGNED, 44 + height INT UNSIGNED, 45 + 46 + UNIQUE KEY `key_phid` (phid), 47 + UNIQUE KEY `key_version` (transactionPHID, commentVersion), 48 + UNIQUE KEY `key_draft` (authorPHID, mockID, transactionPHID) 49 + 50 + ) ENGINE=InnoDB, COLLATE utf8_general_ci; 51 +
+23 -12
src/__phutil_library_map__.php
··· 597 597 'PhabricatorApplicationSlowvote' => 'applications/slowvote/application/PhabricatorApplicationSlowvote.php', 598 598 'PhabricatorApplicationStatusView' => 'applications/meta/view/PhabricatorApplicationStatusView.php', 599 599 'PhabricatorApplicationSubscriptions' => 'applications/subscriptions/application/PhabricatorApplicationSubscriptions.php', 600 + 'PhabricatorApplicationTransaction' => 'applications/transactions/storage/PhabricatorApplicationTransaction.php', 601 + 'PhabricatorApplicationTransactionComment' => 'applications/transactions/storage/PhabricatorApplicationTransactionComment.php', 602 + 'PhabricatorApplicationTransactionCommentEditor' => 'applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php', 603 + 'PhabricatorApplicationTransactionCommentQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php', 604 + 'PhabricatorApplicationTransactionEditor' => 'applications/transactions/editor/PhabricatorApplicationTransactionEditor.php', 605 + 'PhabricatorApplicationTransactionQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionQuery.php', 606 + 'PhabricatorApplicationTransactions' => 'applications/transactions/application/PhabricatorApplicationTransactions.php', 600 607 'PhabricatorApplicationUIExamples' => 'applications/uiexample/application/PhabricatorApplicationUIExamples.php', 601 608 'PhabricatorApplicationsListController' => 'applications/meta/controller/PhabricatorApplicationsListController.php', 602 609 'PhabricatorAuditActionConstants' => 'applications/audit/constants/PhabricatorAuditActionConstants.php', ··· 1129 1136 'PhabricatorTimelineView' => 'view/layout/PhabricatorTimelineView.php', 1130 1137 'PhabricatorTimer' => 'applications/countdown/storage/PhabricatorTimer.php', 1131 1138 'PhabricatorTransactionView' => 'view/layout/PhabricatorTransactionView.php', 1139 + 'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php', 1132 1140 'PhabricatorTransformedFile' => 'applications/files/storage/PhabricatorTransformedFile.php', 1133 1141 'PhabricatorTranslation' => 'infrastructure/internationalization/PhabricatorTranslation.php', 1134 1142 'PhabricatorTrivialTestCase' => 'infrastructure/testing/__tests__/PhabricatorTrivialTestCase.php', ··· 1225 1233 'PholioMockListController' => 'applications/pholio/controller/PholioMockListController.php', 1226 1234 'PholioMockQuery' => 'applications/pholio/query/PholioMockQuery.php', 1227 1235 'PholioMockViewController' => 'applications/pholio/controller/PholioMockViewController.php', 1228 - 'PholioPixelComment' => 'applications/pholio/storage/PholioPixelComment.php', 1229 1236 'PholioReplyHandler' => 'applications/pholio/mail/PholioReplyHandler.php', 1230 1237 'PholioTransaction' => 'applications/pholio/storage/PholioTransaction.php', 1238 + 'PholioTransactionComment' => 'applications/pholio/storage/PholioTransactionComment.php', 1231 1239 'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php', 1232 1240 'PholioTransactionType' => 'applications/pholio/constants/PholioTransactionType.php', 1233 1241 'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php', ··· 1843 1851 'PhabricatorApplicationSlowvote' => 'PhabricatorApplication', 1844 1852 'PhabricatorApplicationStatusView' => 'AphrontView', 1845 1853 'PhabricatorApplicationSubscriptions' => 'PhabricatorApplication', 1854 + 'PhabricatorApplicationTransaction' => 'PhabricatorLiskDAO', 1855 + 'PhabricatorApplicationTransactionComment' => 1856 + array( 1857 + 0 => 'PhabricatorLiskDAO', 1858 + 1 => 'PhabricatorMarkupInterface', 1859 + ), 1860 + 'PhabricatorApplicationTransactionCommentEditor' => 'PhabricatorEditor', 1861 + 'PhabricatorApplicationTransactionCommentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 1862 + 'PhabricatorApplicationTransactionEditor' => 'PhabricatorEditor', 1863 + 'PhabricatorApplicationTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 1864 + 'PhabricatorApplicationTransactions' => 'PhabricatorApplication', 1846 1865 'PhabricatorApplicationUIExamples' => 'PhabricatorApplication', 1847 1866 'PhabricatorApplicationsListController' => 'PhabricatorController', 1848 1867 'PhabricatorAuditAddCommentController' => 'PhabricatorAuditController', ··· 2437 2456 ), 2438 2457 'PholioMockCommentController' => 'PholioController', 2439 2458 'PholioMockEditController' => 'PholioController', 2440 - 'PholioMockEditor' => 'PhabricatorEditor', 2459 + 'PholioMockEditor' => 'PhabricatorApplicationTransactionEditor', 2441 2460 'PholioMockListController' => 'PholioController', 2442 2461 'PholioMockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2443 2462 'PholioMockViewController' => 'PholioController', 2444 - 'PholioPixelComment' => 2445 - array( 2446 - 0 => 'PholioDAO', 2447 - 1 => 'PhabricatorMarkupInterface', 2448 - ), 2449 2463 'PholioReplyHandler' => 'PhabricatorMailReplyHandler', 2450 - 'PholioTransaction' => 2451 - array( 2452 - 0 => 'PholioDAO', 2453 - 1 => 'PhabricatorMarkupInterface', 2454 - ), 2464 + 'PholioTransaction' => 'PhabricatorApplicationTransaction', 2465 + 'PholioTransactionComment' => 'PhabricatorApplicationTransactionComment', 2455 2466 'PholioTransactionQuery' => 'PhabricatorOffsetPagedQuery', 2456 2467 'PholioTransactionType' => 'PholioConstants', 2457 2468 'PhortuneMonthYearExpiryControl' => 'AphrontFormControl',
+3
src/applications/phid/PhabricatorPHIDConstants.php
··· 30 30 const PHID_TYPE_ANSW = 'ANSW'; 31 31 const PHID_TYPE_MOCK = 'MOCK'; 32 32 33 + const PHID_TYPE_XACT = 'XACT'; 34 + const PHID_TYPE_XCMT = 'XCMT'; 35 + 33 36 }
+11 -3
src/applications/phid/storage/PhabricatorPHID.php
··· 7 7 protected $ownerPHID; 8 8 protected $parentPHID; 9 9 10 - public static function generateNewPHID($type) { 10 + public static function generateNewPHID($type, $subtype = null) { 11 11 if (!$type) { 12 12 throw new Exception("Can not generate PHID with no type."); 13 13 } 14 14 15 - $uniq = Filesystem::readRandomCharacters(20); 16 - return 'PHID-'.$type.'-'.$uniq; 15 + if ($subtype === null) { 16 + $uniq_len = 20; 17 + $type_str = "{$type}"; 18 + } else { 19 + $uniq_len = 15; 20 + $type_str = "{$type}-{$subtype}"; 21 + } 22 + 23 + $uniq = Filesystem::readRandomCharacters($uniq_len); 24 + return "PHID-{$type_str}-{$uniq}"; 17 25 } 18 26 19 27 public static function fromObjectName($name) {
-3
src/applications/pholio/constants/PholioTransactionType.php
··· 2 2 3 3 final class PholioTransactionType extends PholioConstants { 4 4 5 - const TYPE_NONE = 'none'; 6 5 const TYPE_NAME = 'name'; 7 6 const TYPE_DESCRIPTION = 'description'; 8 - const TYPE_VIEW_POLICY = 'viewPolicy'; 9 - const TYPE_SUBSCRIBERS = 'subscribers'; 10 7 11 8 }
+4 -2
src/applications/pholio/controller/PholioMockCommentController.php
··· 44 44 )); 45 45 46 46 $xaction = id(new PholioTransaction()) 47 - ->setTransactionType(PholioTransactionType::TYPE_NONE) 48 - ->setComment($comment); 47 + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) 48 + ->attachComment( 49 + id(new PholioTransactionComment()) 50 + ->setContent($comment)); 49 51 50 52 id(new PholioMockEditor()) 51 53 ->setActor($user)
+1 -1
src/applications/pholio/controller/PholioMockEditController.php
··· 57 57 58 58 $type_name = PholioTransactionType::TYPE_NAME; 59 59 $type_desc = PholioTransactionType::TYPE_DESCRIPTION; 60 - $type_view = PholioTransactionType::TYPE_VIEW_POLICY; 60 + $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; 61 61 62 62 $v_name = $request->getStr('name'); 63 63 $v_desc = $request->getStr('description');
+14 -108
src/applications/pholio/controller/PholioMockViewController.php
··· 25 25 } 26 26 27 27 $xactions = id(new PholioTransactionQuery()) 28 - ->withMockIDs(array($mock->getID())) 28 + ->setViewer($user) 29 + ->withObjectPHIDs(array($mock->getPHID())) 29 30 ->execute(); 30 31 31 32 $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( ··· 33 34 34 35 $phids = array(); 35 36 $phids[] = $mock->getAuthorPHID(); 36 - foreach ($xactions as $xaction) { 37 - $phids[] = $xaction->getAuthorPHID(); 38 - foreach ($xaction->getRequiredHandlePHIDs() as $hphid) { 39 - $phids[] = $hphid; 40 - } 41 - } 42 37 foreach ($subscribers as $subscriber) { 43 38 $phids[] = $subscriber; 44 39 } ··· 49 44 ->setViewer($user); 50 45 $engine->addObject($mock, PholioMock::MARKUP_FIELD_DESCRIPTION); 51 46 foreach ($xactions as $xaction) { 52 - $engine->addObject($xaction, PholioTransaction::MARKUP_FIELD_COMMENT); 47 + if ($xaction->getComment()) { 48 + $engine->addObject( 49 + $xaction->getComment(), 50 + PholioTransaction::MARKUP_FIELD_COMMENT); 51 + } 53 52 } 54 53 $engine->process(); 55 54 ··· 198 197 $view = new PhabricatorTimelineView(); 199 198 200 199 foreach ($xactions as $xaction) { 201 - $author = $this->getHandle($xaction->getAuthorPHID()); 202 - 203 - $old = $xaction->getOldValue(); 204 - $new = $xaction->getNewValue(); 205 - 206 - $xaction_visible = true; 207 - $title = null; 208 - $type = $xaction->getTransactionType(); 209 - 210 - switch ($type) { 211 - case PholioTransactionType::TYPE_NONE: 212 - $title = pht( 213 - '%s added a comment.', 214 - $author->renderLink()); 215 - break; 216 - case PholioTransactionType::TYPE_NAME: 217 - if ($old === null) { 218 - $xaction_visible = false; 219 - break; 220 - } 221 - $title = pht( 222 - '%s renamed this mock from "%s" to "%s".', 223 - $author->renderLink(), 224 - phutil_escape_html($old), 225 - phutil_escape_html($new)); 226 - break; 227 - case PholioTransactionType::TYPE_DESCRIPTION: 228 - if ($old === null) { 229 - $xaction_visible = false; 230 - break; 231 - } 232 - // TODO: Show diff, like Maniphest. 233 - $title = pht( 234 - '%s updated the description of this mock. '. 235 - 'The old description was: %s', 236 - $author->renderLink(), 237 - phutil_escape_html($old)); 238 - break; 239 - case PholioTransactionType::TYPE_VIEW_POLICY: 240 - if ($old === null) { 241 - $xaction_visible = false; 242 - break; 243 - } 244 - // TODO: Render human-readable. 245 - $title = pht( 246 - '%s changed the visibility of this mock from "%s" to "%s".', 247 - $author->renderLink(), 248 - phutil_escape_html($old), 249 - phutil_escape_html($new)); 250 - break; 251 - case PholioTransactionType::TYPE_SUBSCRIBERS: 252 - $rem = array_diff($old, $new); 253 - $add = array_diff($new, $old); 254 - 255 - $add_l = array(); 256 - foreach ($add as $phid) { 257 - $add_l[] = $this->getHandle($phid)->renderLink(); 258 - } 259 - $add_l = implode(', ', $add_l); 260 - 261 - $rem_l = array(); 262 - foreach ($rem as $phid) { 263 - $rem_l[] = $this->getHandle($phid)->renderLink(); 264 - } 265 - $rem_l = implode(', ', $rem_l); 266 - 267 - if ($add && $rem) { 268 - $title = pht( 269 - '%s edited subscriber(s), added %d: %s; removed %d: %s.', 270 - $author->renderLink(), 271 - $add_l, 272 - count($add), 273 - $rem_l, 274 - count($rem)); 275 - } else if ($add) { 276 - $title = pht( 277 - '%s added %d subscriber(s): %s.', 278 - $author->renderLink(), 279 - count($add), 280 - $add_l); 281 - } else if ($rem) { 282 - $title = pht( 283 - '%s removed %d subscribers: %s.', 284 - $author->renderLink(), 285 - count($rem), 286 - $rem_l); 287 - } 288 - break; 289 - default: 290 - throw new Exception("Unknown transaction type '{$type}'!"); 200 + if ($xaction->shouldHide()) { 201 + continue; 291 202 } 292 203 293 - if (!$xaction_visible) { 294 - // Some transactions aren't useful to human viewers, like 295 - // the initial transactions which set the mock's name and description. 296 - continue; 297 - } 204 + $title = $xaction->getTitle(); 298 205 299 206 $event = id(new PhabricatorTimelineEventView()) 300 - ->setUserHandle($author); 207 + ->setUserHandle($xaction->getHandle($xaction->getAuthorPHID())) 208 + ->setTitle($title); 301 209 302 - $event->setTitle($title); 303 - 304 - if (strlen($xaction->getComment())) { 210 + if ($xaction->getComment()) { 305 211 $event->appendChild( 306 212 $engine->getOutput( 307 - $xaction, 213 + $xaction->getComment(), 308 214 PholioTransaction::MARKUP_FIELD_COMMENT)); 309 215 } 310 216
+61 -198
src/applications/pholio/editor/PholioMockEditor.php
··· 3 3 /** 4 4 * @group pholio 5 5 */ 6 - final class PholioMockEditor extends PhabricatorEditor { 6 + final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { 7 + 8 + public function getTransactionTypes() { 9 + $types = parent::getTransactionTypes(); 7 10 8 - private $contentSource; 11 + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; 12 + $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; 9 13 10 - public function setContentSource(PhabricatorContentSource $content_source) { 11 - $this->contentSource = $content_source; 12 - return $this; 14 + $types[] = PholioTransactionType::TYPE_NAME; 15 + $types[] = PholioTransactionType::TYPE_DESCRIPTION; 16 + return $types; 13 17 } 14 18 15 - public function getContentSource() { 16 - return $this->contentSource; 19 + protected function didApplyTransactions( 20 + PhabricatorLiskDAO $object, 21 + array $xactions) { 22 + // $this->sendMail($object, $xactions); 23 + // PholioIndexer::indexMock($mock); 24 + return; 17 25 } 18 26 19 - public function applyTransactions(PholioMock $mock, array $xactions) { 20 - assert_instances_of($xactions, 'PholioTransaction'); 27 + protected function getCustomTransactionOldValue( 28 + PhabricatorLiskDAO $object, 29 + PhabricatorApplicationTransaction $xaction) { 21 30 22 - $actor = $this->requireActor(); 23 - if (!$this->contentSource) { 24 - throw new Exception( 25 - "Call setContentSource() before applyTransactions()!"); 31 + switch ($xaction->getTransactionType()) { 32 + case PholioTransactionType::TYPE_NAME: 33 + return $object->getName(); 34 + case PholioTransactionType::TYPE_DESCRIPTION: 35 + return $object->getDescription(); 26 36 } 37 + } 27 38 28 - $is_new = !$mock->getID(); 39 + protected function getCustomTransactionNewValue( 40 + PhabricatorLiskDAO $object, 41 + PhabricatorApplicationTransaction $xaction) { 29 42 30 - $comments = array(); 31 - foreach ($xactions as $xaction) { 32 - if (strlen($xaction->getComment())) { 33 - $comments[] = $xaction->getComment(); 34 - } 35 - $type = $xaction->getTransactionType(); 36 - if ($type == PholioTransactionType::TYPE_DESCRIPTION) { 37 - $comments[] = $xaction->getNewValue(); 38 - } 43 + switch ($xaction->getTransactionType()) { 44 + case PholioTransactionType::TYPE_NAME: 45 + case PholioTransactionType::TYPE_DESCRIPTION: 46 + return $xaction->getNewValue(); 39 47 } 40 - 41 - $mentioned_phids = PhabricatorMarkupEngine::extractPHIDsFromMentions( 42 - $comments); 43 - $subscribe_phids = $mentioned_phids; 44 - 45 - // Attempt to subscribe the actor. 46 - $subscribe_phids[] = $actor->getPHID(); 48 + } 47 49 48 - if ($subscribe_phids) { 49 - if ($mock->getID()) { 50 - $old_subs = PhabricatorSubscribersQuery::loadSubscribersForPHID( 51 - $mock->getPHID()); 52 - } else { 53 - $old_subs = array(); 54 - } 50 + protected function applyCustomInternalTransaction( 51 + PhabricatorLiskDAO $object, 52 + PhabricatorApplicationTransaction $xaction) { 55 53 56 - $new_subs = array_merge($old_subs, $mentioned_phids); 57 - $xaction = id(new PholioTransaction()) 58 - ->setTransactionType(PholioTransactionType::TYPE_SUBSCRIBERS) 59 - ->setOldValue($old_subs) 60 - ->setNewValue($new_subs); 61 - array_unshift($xactions, $xaction); 54 + switch ($xaction->getTransactionType()) { 55 + case PholioTransactionType::TYPE_NAME: 56 + $object->setName($xaction->getNewValue()); 57 + if ($object->getOriginalName() === null) { 58 + $object->setOriginalName($xaction->getNewValue()); 59 + } 60 + break; 61 + case PholioTransactionType::TYPE_DESCRIPTION: 62 + $object->setDescription($xaction->getNewValue()); 63 + break; 62 64 } 65 + } 63 66 64 - foreach ($xactions as $xaction) { 65 - $xaction->setContentSource($this->contentSource); 66 - $xaction->setAuthorPHID($actor->getPHID()); 67 - } 67 + protected function applyCustomExternalTransaction( 68 + PhabricatorLiskDAO $object, 69 + PhabricatorApplicationTransaction $xaction) { 70 + return; 71 + } 68 72 69 - foreach ($xactions as $key => $xaction) { 70 - $has_effect = $this->applyTransaction($mock, $xaction); 71 - if (!$has_effect) { 72 - unset($xactions[$key]); 73 - } 74 - } 73 + protected function mergeTransactions( 74 + PhabricatorApplicationTransaction $u, 75 + PhabricatorApplicationTransaction $v) { 75 76 76 - if (!$xactions) { 77 - return; 77 + $type = $u->getTransactionType(); 78 + switch ($type) { 79 + case PholioTransactionType::TYPE_NAME: 80 + case PholioTransactionType::TYPE_DESCRIPTION: 81 + return $v; 78 82 } 79 83 80 - $mock->openTransaction(); 81 - $mock->save(); 84 + return parent::mergeTransactions($u, $v); 85 + } 82 86 83 - foreach ($xactions as $xaction) { 84 - $xaction->setMockID($mock->getID()); 85 - $xaction->save(); 86 - } 87 - 88 - // Apply ID/PHID-dependent transactions. 89 - foreach ($xactions as $xaction) { 90 - $type = $xaction->getTransactionType(); 91 - switch ($type) { 92 - case PholioTransactionType::TYPE_SUBSCRIBERS: 93 - $subeditor = id(new PhabricatorSubscriptionsEditor()) 94 - ->setObject($mock) 95 - ->setActor($this->requireActor()) 96 - ->subscribeExplicit($xaction->getNewValue()) 97 - ->save(); 98 - break; 99 - } 100 - } 101 - 102 - $mock->saveTransaction(); 103 - 104 - $this->sendMail($mock, $xactions, $is_new, $mentioned_phids); 105 - 106 - PholioIndexer::indexMock($mock); 107 - 108 - return $this; 109 - } 110 87 111 88 private function sendMail( 112 89 PholioMock $mock, ··· 194 171 return PhabricatorEnv::getEnvConfig('metamta.pholio.subject-prefix'); 195 172 } 196 173 197 - private function applyTransaction( 198 - PholioMock $mock, 199 - PholioTransaction $xaction) { 200 - 201 - $type = $xaction->getTransactionType(); 202 - 203 - $old = null; 204 - switch ($type) { 205 - case PholioTransactionType::TYPE_NONE: 206 - $old = null; 207 - break; 208 - case PholioTransactionType::TYPE_NAME: 209 - $old = $mock->getName(); 210 - break; 211 - case PholioTransactionType::TYPE_DESCRIPTION: 212 - $old = $mock->getDescription(); 213 - break; 214 - case PholioTransactionType::TYPE_VIEW_POLICY: 215 - $old = $mock->getViewPolicy(); 216 - break; 217 - case PholioTransactionType::TYPE_SUBSCRIBERS: 218 - $old = PhabricatorSubscribersQuery::loadSubscribersForPHID( 219 - $mock->getPHID()); 220 - break; 221 - default: 222 - throw new Exception("Unknown transaction type '{$type}'!"); 223 - } 224 - 225 - $xaction->setOldValue($old); 226 - 227 - if (!$this->transactionHasEffect($mock, $xaction)) { 228 - return false; 229 - } 230 - 231 - switch ($type) { 232 - case PholioTransactionType::TYPE_NONE: 233 - break; 234 - case PholioTransactionType::TYPE_NAME: 235 - $mock->setName($xaction->getNewValue()); 236 - if ($mock->getOriginalName() === null) { 237 - $mock->setOriginalName($xaction->getNewValue()); 238 - } 239 - break; 240 - case PholioTransactionType::TYPE_DESCRIPTION: 241 - $mock->setDescription($xaction->getNewValue()); 242 - break; 243 - case PholioTransactionType::TYPE_VIEW_POLICY: 244 - $mock->setViewPolicy($xaction->getNewValue()); 245 - break; 246 - case PholioTransactionType::TYPE_SUBSCRIBERS: 247 - // This applies later. 248 - break; 249 - default: 250 - throw new Exception("Unknown transaction type '{$type}'!"); 251 - } 252 - 253 - return true; 254 - } 255 - 256 - private function transactionHasEffect( 257 - PholioMock $mock, 258 - PholioTransaction $xaction) { 259 - 260 - $effect = false; 261 - 262 - $old = $xaction->getOldValue(); 263 - $new = $xaction->getNewValue(); 264 - 265 - $type = $xaction->getTransactionType(); 266 - switch ($type) { 267 - case PholioTransactionType::TYPE_NONE: 268 - case PholioTransactionType::TYPE_NAME: 269 - case PholioTransactionType::TYPE_DESCRIPTION: 270 - case PholioTransactionType::TYPE_VIEW_POLICY: 271 - $effect = ($old !== $new); 272 - break; 273 - case PholioTransactionType::TYPE_SUBSCRIBERS: 274 - $old = nonempty($old, array()); 275 - $old_map = array_fill_keys($old, true); 276 - $filtered = $old; 277 - 278 - foreach ($new as $phid) { 279 - if ($mock->getAuthorPHID() == $phid) { 280 - // The author may not be explicitly subscribed. 281 - continue; 282 - } 283 - if (isset($old_map[$phid])) { 284 - // This PHID was already subscribed. 285 - continue; 286 - } 287 - $filtered[] = $phid; 288 - } 289 - 290 - $old = array_keys($old_map); 291 - $new = array_values($filtered); 292 - 293 - $xaction->setOldValue($old); 294 - $xaction->setNewValue($new); 295 - 296 - $effect = ($old !== $new); 297 - break; 298 - default: 299 - throw new Exception("Unknown transaction type '{$type}'!"); 300 - } 301 - 302 - if (!$effect) { 303 - if (strlen($xaction->getComment())) { 304 - $xaction->setTransactionType(PholioTransactionType::TYPE_NONE); 305 - $effect = true; 306 - } 307 - } 308 - 309 - return $effect; 310 - } 311 174 312 175 }
+3 -38
src/applications/pholio/query/PholioTransactionQuery.php
··· 4 4 * @group pholio 5 5 */ 6 6 final class PholioTransactionQuery 7 - extends PhabricatorOffsetPagedQuery { 8 - 9 - private $mockIDs; 7 + extends PhabricatorApplicationTransactionQuery { 10 8 11 - public function withMockIDs(array $ids) { 12 - $this->mockIDs = $ids; 13 - return $this; 14 - } 15 - 16 - public function execute() { 17 - $table = new PholioTransaction(); 18 - $conn_r = $table->establishConnection('r'); 19 - 20 - $data = queryfx_all( 21 - $conn_r, 22 - 'SELECT * FROM %T x %Q %Q %Q', 23 - $table->getTableName(), 24 - $this->buildWhereClause($conn_r), 25 - $this->buildOrderClause($conn_r), 26 - $this->buildLimitClause($conn_r)); 27 - 28 - return $table->loadAllFromArray($data); 29 - } 30 - 31 - private function buildWhereClause(AphrontDatabaseConnection $conn_r) { 32 - $where = array(); 33 - 34 - if ($this->mockIDs) { 35 - $where[] = qsprintf( 36 - $conn_r, 37 - 'mockID IN (%Ld)', 38 - $this->mockIDs); 39 - } 40 - 41 - return $this->formatWhereClause($where); 42 - } 43 - 44 - private function buildOrderClause(AphrontDatabaseConnection $conn_r) { 45 - return 'ORDER BY id ASC'; 9 + protected function getTemplateApplicationTransaction() { 10 + return new PholioTransaction(); 46 11 } 47 12 48 13 }
-46
src/applications/pholio/storage/PholioPixelComment.php
··· 1 - <?php 2 - 3 - /** 4 - * @group pholio 5 - */ 6 - final class PholioPixelComment extends PholioDAO 7 - implements PhabricatorMarkupInterface { 8 - 9 - const MARKUP_FIELD_COMMENT = 'markup:comment'; 10 - 11 - protected $mockID; 12 - protected $transactionID; 13 - protected $authorPHID; 14 - protected $imageID; 15 - 16 - protected $x; 17 - protected $y; 18 - protected $width; 19 - protected $height; 20 - protected $comment; 21 - 22 - 23 - /* -( PhabricatorMarkupInterface )----------------------------------------- */ 24 - 25 - 26 - public function getMarkupFieldKey($field) { 27 - return 'MP:'.$this->getID(); 28 - } 29 - 30 - public function newMarkupEngine($field) { 31 - return PhabricatorMarkupEngine::newMarkupEngine(array()); 32 - } 33 - 34 - public function getMarkupText($field) { 35 - return $this->getComment(); 36 - } 37 - 38 - public function didMarkupText($field, $output, PhutilMarkupEngine $engine) { 39 - return $output; 40 - } 41 - 42 - public function shouldUseMarkupCache($field) { 43 - return ($this->getID() && $this->getTransactionID()); 44 - } 45 - 46 - }
+36 -80
src/applications/pholio/storage/PholioTransaction.php
··· 3 3 /** 4 4 * @group pholio 5 5 */ 6 - final class PholioTransaction extends PholioDAO 7 - implements PhabricatorMarkupInterface { 8 - 9 - const MARKUP_FIELD_COMMENT = 'markup:comment'; 6 + final class PholioTransaction extends PhabricatorApplicationTransaction { 10 7 11 - protected $mockID; 12 - protected $authorPHID; 13 - protected $transactionType; 14 - protected $oldValue; 15 - protected $newValue; 16 - protected $comment = ''; 17 - protected $metadata = array(); 18 - protected $contentSource; 19 - 20 - public function getConfiguration() { 21 - return array( 22 - self::CONFIG_SERIALIZATION => array( 23 - 'oldValue' => self::SERIALIZATION_JSON, 24 - 'newValue' => self::SERIALIZATION_JSON, 25 - 'metadata' => self::SERIALIZATION_JSON, 26 - ), 27 - ) + parent::getConfiguration(); 28 - } 29 - 30 - public function setContentSource(PhabricatorContentSource $content_source) { 31 - $this->contentSource = $content_source->serialize(); 32 - return $this; 8 + public function getApplicationName() { 9 + return 'pholio'; 33 10 } 34 11 35 - public function getContentSource() { 36 - return PhabricatorContentSource::newFromSerialized($this->contentSource); 12 + public function getApplicationTransactionType() { 13 + return PhabricatorPHIDConstants::PHID_TYPE_MOCK; 37 14 } 38 15 39 - public function getRequiredHandlePHIDs() { 40 - switch ($this->getTransactionType()) { 41 - case PholioTransactionType::TYPE_SUBSCRIBERS: 42 - return array_merge( 43 - $this->getOldValue(), 44 - $this->getNewValue()); 45 - default: 46 - return array(); 47 - } 16 + public function getApplicationTransactionCommentObject() { 17 + return new PholioTransactionComment(); 48 18 } 49 19 50 - 51 - /* -( PhabricatorSubscribableInterface Implementation )-------------------- */ 52 - 53 - 54 - public function isAutomaticallySubscribed($phid) { 55 - return ($this->authorPHID == $phid); 20 + public function getApplicationObjectTypeName() { 21 + return pht('mock'); 56 22 } 57 23 58 - 59 - /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ 60 - 61 - 62 - public function getCapabilities() { 63 - return array( 64 - PhabricatorPolicyCapability::CAN_VIEW, 65 - PhabricatorPolicyCapability::CAN_EDIT, 66 - ); 67 - } 24 + public function shouldHide() { 25 + $old = $this->getOldValue(); 68 26 69 - public function getPolicy($capability) { 70 - switch ($capability) { 71 - case PhabricatorPolicyCapability::CAN_VIEW: 72 - return $this->getViewPolicy(); 73 - case PhabricatorPolicyCapability::CAN_EDIT: 74 - return PhabricatorPolicies::POLICY_NOONE; 27 + switch ($this->getTransactionType()) { 28 + case PholioTransactionType::TYPE_NAME: 29 + case PholioTransactionType::TYPE_DESCRIPTION: 30 + return ($old === null); 75 31 } 76 - } 77 32 78 - public function hasAutomaticCapbility($capability, PhabricatorUser $viewer) { 79 - return ($viewer->getPHID() == $this->getAuthorPHID()); 33 + return parent::shouldHide(); 80 34 } 81 35 82 - 83 - /* -( PhabricatorMarkupInterface )----------------------------------------- */ 84 - 85 - 86 - public function getMarkupFieldKey($field) { 87 - return 'MX:'.$this->getID(); 88 - } 36 + public function getTitle() { 37 + $author_phid = $this->getAuthorPHID(); 89 38 90 - public function newMarkupEngine($field) { 91 - return PhabricatorMarkupEngine::newMarkupEngine(array()); 92 - } 39 + $old = $this->getOldValue(); 40 + $new = $this->getNewValue(); 93 41 94 - public function getMarkupText($field) { 95 - return $this->getComment(); 96 - } 42 + switch ($this->getTransactionType()) { 43 + case PholioTransactionType::TYPE_NAME: 44 + return pht( 45 + '%s renamed this mock from "%s" to "%s".', 46 + $this->renderHandleLink($author_phid), 47 + phutil_escape_html($old), 48 + phutil_escape_html($new)); 49 + break; 50 + case PholioTransactionType::TYPE_DESCRIPTION: 51 + return pht( 52 + '%s updated the description of this mock. '. 53 + 'The old description was: %s', 54 + $this->renderHandleLink($author_phid), 55 + phutil_escape_html($old)); 56 + } 97 57 98 - public function didMarkupText($field, $output, PhutilMarkupEngine $engine) { 99 - return $output; 58 + return parent::getTitle(); 100 59 } 101 60 102 - public function shouldUseMarkupCache($field) { 103 - return (bool)$this->getID(); 104 - } 105 61 106 62 }
+19
src/applications/pholio/storage/PholioTransactionComment.php
··· 1 + <?php 2 + 3 + /** 4 + * @group pholio 5 + */ 6 + final class PholioTransactionComment 7 + extends PhabricatorApplicationTransactionComment { 8 + 9 + protected $imageID; 10 + protected $x; 11 + protected $y; 12 + protected $width; 13 + protected $height; 14 + 15 + public function getApplicationTransactionObject() { 16 + return new PholioTransaction(); 17 + } 18 + 19 + }
+15
src/applications/transactions/application/PhabricatorApplicationTransactions.php
··· 1 + <?php 2 + 3 + final class PhabricatorApplicationTransactions extends PhabricatorApplication { 4 + 5 + public function shouldAppearInLaunchView() { 6 + return false; 7 + } 8 + 9 + public function getRoutes() { 10 + return array( 11 + ); 12 + } 13 + 14 + } 15 +
+10
src/applications/transactions/constants/PhabricatorTransactions.php
··· 1 + <?php 2 + 3 + final class PhabricatorTransactions { 4 + 5 + const TYPE_COMMENT = 'core:comment'; 6 + const TYPE_SUBSCRIBERS = 'core:subscribers'; 7 + const TYPE_VIEW_POLICY = 'core:view-policy'; 8 + const TYPE_EDIT_POLICY = 'core:edit-policy'; 9 + 10 + }
+102
src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php
··· 1 + <?php 2 + 3 + final class PhabricatorApplicationTransactionCommentEditor 4 + extends PhabricatorEditor { 5 + 6 + private $contentSource; 7 + 8 + public function setContentSource(PhabricatorContentSource $content_source) { 9 + $this->contentSource = $content_source; 10 + return $this; 11 + } 12 + 13 + public function getContentSource() { 14 + return $this->contentSource; 15 + } 16 + 17 + /** 18 + * Edit a transaction's comment. This method effects the required create, 19 + * update or delete to set the transaction's comment to the provided comment. 20 + */ 21 + public function applyEdit( 22 + PhabricatorApplicationTransaction $xaction, 23 + PhabricatorApplicationTransactionComment $comment) { 24 + 25 + $this->validateEdit($xaction, $comment); 26 + 27 + $actor = $this->requireActor(); 28 + 29 + $comment->setContentSource($this->getContentSource()); 30 + $comment->setAuthorPHID($actor->getPHID()); 31 + 32 + // TODO: This needs to be more sophisticated once we have meta-policies. 33 + $comment->setViewPolicy(PhabricatorPolicies::POLICY_PUBLIC); 34 + $comment->setEditPolicy($actor->getPHID()); 35 + 36 + $xaction->openTransaction(); 37 + $xaction->beginReadLocking(); 38 + if ($xaction->getID()) { 39 + $xaction->reload(); 40 + } 41 + 42 + $new_version = $xaction->getCommentVersion() + 1; 43 + 44 + $comment->setCommentVersion($new_version); 45 + $comment->setTransactionPHID($xaction->getPHID()); 46 + $comment->save(); 47 + 48 + $xaction->setCommentVersion($new_version); 49 + $xaction->setCommentPHID($comment->getPHID()); 50 + $xaction->setViewPolicy($comment->getViewPolicy()); 51 + $xaction->setEditPolicy($comment->getEditPolicy()); 52 + $xaction->save(); 53 + 54 + $xaction->endReadLocking(); 55 + $xaction->saveTransaction(); 56 + 57 + $xaction->attachComment($comment); 58 + 59 + // TODO: Emit an event for notifications/feed? Can we handle them 60 + // generically? 61 + 62 + return $this; 63 + } 64 + 65 + /** 66 + * Validate that the edit is permissible, and the actor has permission to 67 + * perform it. 68 + */ 69 + private function validateEdit( 70 + PhabricatorApplicationTransaction $xaction, 71 + PhabricatorApplicationTransactionComment $comment) { 72 + 73 + if (!$xaction->getPHID()) { 74 + throw new Exception( 75 + "Transaction must have a PHID before calling applyEdit()!"); 76 + } 77 + 78 + if ($comment->getPHID()) { 79 + throw new Exception( 80 + "Transaction comment must not yet have a PHID!"); 81 + } 82 + 83 + if (!$this->getContentSource()) { 84 + throw new Exception( 85 + "Call setContentSource() before applyEdit()!"); 86 + } 87 + 88 + $actor = $this->requireActor(); 89 + 90 + PhabricatorPolicyFilter::requireCapability( 91 + $actor, 92 + $xaction, 93 + PhabricatorPolicyCapability::CAN_VIEW); 94 + 95 + PhabricatorPolicyFilter::requireCapability( 96 + $actor, 97 + $xaction, 98 + PhabricatorPolicyCapability::CAN_EDIT); 99 + } 100 + 101 + 102 + }
+487
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorApplicationTransactionEditor 4 + extends PhabricatorEditor { 5 + 6 + private $contentSource; 7 + private $object; 8 + private $xactions; 9 + 10 + private $isNewObject; 11 + private $mentionedPHIDs; 12 + 13 + protected function getIsNewObject() { 14 + return $this->isNewObject; 15 + } 16 + 17 + protected function getMentionedPHIDs() { 18 + return $this->mentionedPHIDs; 19 + } 20 + 21 + public function getTransactionTypes() { 22 + $types = array( 23 + PhabricatorTransactions::TYPE_COMMENT, 24 + ); 25 + 26 + if ($this->object instanceof PhabricatorSubscribableInterface) { 27 + $types[] = PhabricatorTransactions::TYPE_SUBSCRIBERS; 28 + } 29 + 30 + return $types; 31 + } 32 + 33 + private function adjustTransactionValues( 34 + PhabricatorLiskDAO $object, 35 + PhabricatorApplicationTransaction $xaction) { 36 + $old = $this->getTransactionOldValue($object, $xaction); 37 + $xaction->setOldValue($old); 38 + 39 + $new = $this->getTransactionNewValue($object, $xaction); 40 + $xaction->setNewValue($new); 41 + } 42 + 43 + private function getTransactionOldValue( 44 + PhabricatorLiskDAO $object, 45 + PhabricatorApplicationTransaction $xaction) { 46 + switch ($xaction->getTransactionType()) { 47 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 48 + if ($object->getPHID()) { 49 + $old_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( 50 + $object->getPHID()); 51 + } else { 52 + $old_phids = array(); 53 + } 54 + return array_values($old_phids); 55 + case PhabricatorTransactions::TYPE_VIEW_POLICY: 56 + return $object->getViewPolicy(); 57 + case PhabricatorTransactions::TYPE_EDIT_POLICY: 58 + return $object->getEditPolicy(); 59 + default: 60 + return $this->getCustomTransactionOldValue($object, $xaction); 61 + } 62 + } 63 + 64 + private function getTransactionNewValue( 65 + PhabricatorLiskDAO $object, 66 + PhabricatorApplicationTransaction $xaction) { 67 + switch ($xaction->getTransactionType()) { 68 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 69 + return $this->getPHIDTransactionNewValue($xaction); 70 + case PhabricatorTransactions::TYPE_VIEW_POLICY: 71 + case PhabricatorTransactions::TYPE_EDIT_POLICY: 72 + return $xaction->getNewValue(); 73 + default: 74 + return $this->getCustomTransactionNewValue($object, $xaction); 75 + } 76 + } 77 + 78 + protected function getCustomTransactionOldValue( 79 + PhabricatorLiskDAO $object, 80 + PhabricatorApplicationTransaction $xaction) { 81 + throw new Exception("Capability not supported!"); 82 + } 83 + 84 + protected function getCustomTransactionNewValue( 85 + PhabricatorLiskDAO $object, 86 + PhabricatorApplicationTransaction $xaction) { 87 + throw new Exception("Capability not supported!"); 88 + } 89 + 90 + protected function transactionHasEffect( 91 + PhabricatorLiskDAO $object, 92 + PhabricatorApplicationTransaction $xaction) { 93 + return ($xaction->getOldValue() !== $xaction->getNewValue()); 94 + } 95 + 96 + private function applyInternalEffects( 97 + PhabricatorLiskDAO $object, 98 + PhabricatorApplicationTransaction $xaction) { 99 + switch ($xaction->getTransactionType()) { 100 + case PhabricatorTransactions::TYPE_COMMENT: 101 + break; 102 + case PhabricatorTransactions::TYPE_VIEW_POLICY: 103 + $object->setViewPolicy($xaction->getNewValue()); 104 + break; 105 + case PhabricatorTransactions::TYPE_EDIT_POLICY: 106 + $object->setEditPolicy($xaction->getNewValue()); 107 + break; 108 + default: 109 + return $this->applyCustomInternalTransaction($object, $xaction); 110 + } 111 + } 112 + 113 + private function applyExternalEffects( 114 + PhabricatorLiskDAO $object, 115 + PhabricatorApplicationTransaction $xaction) { 116 + switch ($xaction->getTransactionType()) { 117 + case PhabricatorTransactions::TYPE_COMMENT: 118 + break; 119 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 120 + $subeditor = id(new PhabricatorSubscriptionsEditor()) 121 + ->setObject($object) 122 + ->setActor($this->requireActor()) 123 + ->subscribeExplicit($xaction->getNewValue()) 124 + ->save(); 125 + break; 126 + default: 127 + return $this->applyCustomExternalTransaction($object, $xaction); 128 + } 129 + } 130 + 131 + protected function applyCustomInternalTransaction( 132 + PhabricatorLiskDAO $object, 133 + PhabricatorApplicationTransaction $xaction) { 134 + throw new Exception("Capability not supported!"); 135 + } 136 + 137 + protected function applyCustomExternalTransaction( 138 + PhabricatorLiskDAO $object, 139 + PhabricatorApplicationTransaction $xaction) { 140 + throw new Exception("Capability not supported!"); 141 + } 142 + 143 + public function setContentSource(PhabricatorContentSource $content_source) { 144 + $this->contentSource = $content_source; 145 + return $this; 146 + } 147 + 148 + public function getContentSource() { 149 + return $this->contentSource; 150 + } 151 + 152 + protected function didApplyTransactions( 153 + PhabricatorLiskDAO $object, 154 + array $xactions) { 155 + return; 156 + } 157 + 158 + final public function applyTransactions( 159 + PhabricatorLiskDAO $object, 160 + array $xactions) { 161 + 162 + $this->object = $object; 163 + $this->xactions = $xactions; 164 + $this->isNewObject = ($object->getPHID() === null); 165 + 166 + $this->validateEditParameters($object, $xactions); 167 + 168 + 169 + $actor = $this->requireActor(); 170 + 171 + $mention_xaction = $this->buildMentionTransaction($object, $xactions); 172 + if ($mention_xaction) { 173 + $xactions[] = $mention_xaction; 174 + } 175 + 176 + $xactions = $this->combineTransactions($xactions); 177 + 178 + foreach ($xactions as $xaction) { 179 + // TODO: This needs to be more sophisticated once we have meta-policies. 180 + $xaction->setViewPolicy(PhabricatorPolicies::POLICY_PUBLIC); 181 + $xaction->setEditPolicy($actor->getPHID()); 182 + 183 + $xaction->setAuthorPHID($actor->getPHID()); 184 + $xaction->setContentSource($this->getContentSource()); 185 + } 186 + 187 + foreach ($xactions as $xaction) { 188 + $this->adjustTransactionValues($object, $xaction); 189 + } 190 + 191 + foreach ($xactions as $key => $xaction) { 192 + if (!$this->transactionHasEffect($object, $xaction)) { 193 + // TODO: Raise these to the user. 194 + if ($xaction->getComment()) { 195 + $xaction->setTransactionType( 196 + PhabricatorTransactions::TYPE_COMMENT); 197 + $xaction->setOldValue(null); 198 + $xaction->setNewValue(null); 199 + } else { 200 + unset($xactions[$key]); 201 + } 202 + } 203 + } 204 + 205 + $xactions = $this->sortTransactions($xactions); 206 + 207 + $comment_editor = id(new PhabricatorApplicationTransactionCommentEditor()) 208 + ->setActor($actor) 209 + ->setContentSource($this->getContentSource()); 210 + 211 + $object->openTransaction(); 212 + foreach ($xactions as $xaction) { 213 + $this->applyInternalEffects($object, $xaction); 214 + } 215 + 216 + $object->save(); 217 + 218 + foreach ($xactions as $xaction) { 219 + $xaction->setObjectPHID($object->getPHID()); 220 + if ($xaction->getComment()) { 221 + $xaction->setPHID($xaction->generatePHID()); 222 + $comment_editor->applyEdit($xaction, $xaction->getComment()); 223 + } else { 224 + $xaction->save(); 225 + } 226 + } 227 + 228 + foreach ($xactions as $xaction) { 229 + $this->applyExternalEffects($object, $xaction); 230 + } 231 + $object->saveTransaction(); 232 + 233 + // TODO: Send mail. 234 + // TODO: Index object. 235 + // TODO: Publish feed/notifications. 236 + 237 + $this->didApplyTransactions($object, $xactions); 238 + 239 + return $this; 240 + } 241 + 242 + private function validateEditParameters( 243 + PhabricatorLiskDAO $object, 244 + array $xactions) { 245 + 246 + if (!$this->getContentSource()) { 247 + throw new Exception( 248 + "Call setContentSource() before applyTransactions()!"); 249 + } 250 + 251 + // Do a bunch of sanity checks that the incoming transactions are fresh. 252 + // They should be unsaved and have only "transactionType" and "newValue" 253 + // set. 254 + 255 + $types = array_fill_keys($this->getTransactionTypes(), true); 256 + 257 + assert_instances_of($xactions, 'PhabricatorApplicationTransaction'); 258 + foreach ($xactions as $xaction) { 259 + if ($xaction->getPHID() || $xaction->getID()) { 260 + throw new Exception( 261 + "You can not apply transactions which already have IDs/PHIDs!"); 262 + } 263 + if ($xaction->getObjectPHID()) { 264 + throw new Exception( 265 + "You can not apply transactions which already have objectPHIDs!"); 266 + } 267 + if ($xaction->getAuthorPHID()) { 268 + throw new Exception( 269 + "You can not apply transactions which already have authorPHIDs!"); 270 + } 271 + if ($xaction->getCommentPHID()) { 272 + throw new Exception( 273 + "You can not apply transactions which already have commentPHIDs!"); 274 + } 275 + if ($xaction->getCommentVersion() !== 0) { 276 + throw new Exception( 277 + "You can not apply transactions which already have commentVersions!"); 278 + } 279 + if ($xaction->getOldValue() !== null) { 280 + throw new Exception( 281 + "You can not apply transactions which already have oldValue!"); 282 + } 283 + 284 + $type = $xaction->getTransactionType(); 285 + if (empty($types[$type])) { 286 + throw new Exception("Transaction has unknown type '{$type}'."); 287 + } 288 + } 289 + 290 + // The actor must have permission to view and edit the object. 291 + 292 + $actor = $this->requireActor(); 293 + 294 + PhabricatorPolicyFilter::requireCapability( 295 + $actor, 296 + $xaction, 297 + PhabricatorPolicyCapability::CAN_VIEW); 298 + 299 + PhabricatorPolicyFilter::requireCapability( 300 + $actor, 301 + $xaction, 302 + PhabricatorPolicyCapability::CAN_EDIT); 303 + } 304 + 305 + private function buildMentionTransaction( 306 + PhabricatorLiskDAO $object, 307 + array $xactions) { 308 + 309 + if (!($object instanceof PhabricatorSubscribableInterface)) { 310 + return null; 311 + } 312 + 313 + $texts = array(); 314 + foreach ($xactions as $xaction) { 315 + $texts[] = $this->getMentionableTextsFromTransaction($xaction); 316 + } 317 + $texts = array_mergev($texts); 318 + 319 + $phids = PhabricatorMarkupEngine::extractPHIDsFromMentions($texts); 320 + 321 + $this->mentionedPHIDs = $phids; 322 + 323 + if (!$phids) { 324 + return null; 325 + } 326 + 327 + $xaction = newv(get_class(head($xactions)), array()); 328 + $xaction->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS); 329 + $xaction->setNewValue(array('+' => $phids)); 330 + 331 + return $xaction; 332 + } 333 + 334 + protected function getMentionableTextsFromTransaction( 335 + PhabricatorApplicationTransaction $transaction) { 336 + $texts = array(); 337 + if ($transaction->getComment()) { 338 + $texts[] = $transaction->getComment()->getContent(); 339 + } 340 + return $texts; 341 + } 342 + 343 + protected function mergeTransactions( 344 + PhabricatorApplicationTransaction $u, 345 + PhabricatorApplicationTransaction $v) { 346 + 347 + $type = $u->getTransactionType(); 348 + 349 + switch ($type) { 350 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 351 + return $this->mergePHIDTransactions($u, $v); 352 + } 353 + 354 + // By default, do not merge the transactions. 355 + return null; 356 + } 357 + 358 + 359 + /** 360 + * Attempt to combine similar transactions into a smaller number of total 361 + * transactions. For example, two transactions which edit the title of an 362 + * object can be merged into a single edit. 363 + */ 364 + private function combineTransactions(array $xactions) { 365 + $stray_comments = array(); 366 + 367 + $result = array(); 368 + $types = array(); 369 + foreach ($xactions as $key => $xaction) { 370 + $type = $xaction->getTransactionType(); 371 + if (isset($types[$type])) { 372 + foreach ($types[$type] as $other_key) { 373 + $merged = $this->mergeTransactions($result[$other_key], $xaction); 374 + if ($merged) { 375 + $result[$other_key] = $merged; 376 + 377 + if ($xaction->getComment() && 378 + ($xaction->getComment() !== $merged->getComment())) { 379 + $stray_comments[] = $xaction->getComment(); 380 + } 381 + 382 + if ($result[$other_key]->getComment() && 383 + ($result[$other_key]->getComment() !== $merged->getComment())) { 384 + $stray_comments[] = $result[$other_key]->getComment(); 385 + } 386 + 387 + // Move on to the next transaction. 388 + continue 2; 389 + } 390 + } 391 + } 392 + $result[$key] = $xaction; 393 + $types[$type][] = $key; 394 + } 395 + 396 + // If we merged any comments away, restore them. 397 + foreach ($stray_comments as $comment) { 398 + $xaction = newv(get_class(head($result)), array()); 399 + $xaction->setTransactionType(PhabricatorTransactions::TYPE_COMMENT); 400 + $xaction->setComment($comment); 401 + $result[] = $xaction; 402 + } 403 + 404 + return array_values($result); 405 + } 406 + 407 + protected function mergePHIDTransactions( 408 + PhabricatorApplicationTransaction $u, 409 + PhabricatorApplicationTransaction $v) { 410 + 411 + $result = $u->getNewValue(); 412 + foreach ($v->getNewValue() as $key => $value) { 413 + $result[$key] = array_merge($value, idx($result, $key, array())); 414 + } 415 + $u->setNewValue($result); 416 + 417 + return $u; 418 + } 419 + 420 + 421 + protected function getPHIDTransactionNewValue( 422 + PhabricatorApplicationTransaction $xaction) { 423 + 424 + $old = array_combine($xaction->getOldValue(), $xaction->getOldValue()); 425 + 426 + $new = $xaction->getNewValue(); 427 + $new_add = idx($new, '+', array()); 428 + unset($new['+']); 429 + $new_rem = idx($new, '-', array()); 430 + unset($new['-']); 431 + $new_set = idx($new, '=', null); 432 + if ($new_set !== null) { 433 + $new_set = array_combine($new_set, $new_set); 434 + } 435 + unset($new['=']); 436 + 437 + if ($new) { 438 + throw new Exception( 439 + "Invalid 'new' value for PHID transaction. Value should contain only ". 440 + "keys '+' (add PHIDs), '-' (remove PHIDs) and '=' (set PHIDS)."); 441 + } 442 + 443 + $result = array(); 444 + 445 + foreach ($old as $phid) { 446 + if ($new_set !== null && empty($new_set[$phid])) { 447 + continue; 448 + } 449 + $result[$phid] = $phid; 450 + } 451 + 452 + if ($new_set !== null) { 453 + foreach ($new_set as $phid) { 454 + $result[$phid] = $phid; 455 + } 456 + } 457 + 458 + foreach ($new_add as $phid) { 459 + $result[$phid] = $phid; 460 + } 461 + 462 + foreach ($new_rem as $phid) { 463 + unset($result[$phid]); 464 + } 465 + 466 + return array_values($result); 467 + } 468 + 469 + protected function sortTransactions(array $xactions) { 470 + $head = array(); 471 + $tail = array(); 472 + 473 + // Move bare comments to the end, so the actions preceed them. 474 + foreach ($xactions as $xaction) { 475 + $type = $xaction->getTransactionType(); 476 + if ($type == PhabricatorTransactions::TYPE_COMMENT) { 477 + $tail[] = $xaction; 478 + } else { 479 + $head[] = $xaction; 480 + } 481 + } 482 + 483 + return array_values(array_merge($head, $tail)); 484 + } 485 + 486 + 487 + }
+62
src/applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorApplicationTransactionCommentQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $template; 7 + 8 + private $phids; 9 + private $transactionPHIDs; 10 + 11 + public function setTemplate( 12 + PhabricatorApplicationTransactionComment $template) { 13 + $this->template = $template; 14 + return $this; 15 + } 16 + 17 + public function withPHIDs(array $phids) { 18 + $this->phids = $phids; 19 + return $this; 20 + } 21 + 22 + public function withTranactionPHIDs(array $transaction_phids) { 23 + $this->transactionPHIDs = $transaction_phids; 24 + return $this; 25 + } 26 + 27 + public function loadPage() { 28 + $table = $this->template; 29 + $conn_r = $table->establishConnection('r'); 30 + 31 + $data = queryfx_all( 32 + $conn_r, 33 + 'SELECT * FROM %T xc %Q %Q %Q', 34 + $table->getTableName(), 35 + $this->buildWhereClause($conn_r), 36 + $this->buildOrderClause($conn_r), 37 + $this->buildLimitClause($conn_r)); 38 + 39 + return $table->loadAllFromArray($data); 40 + } 41 + 42 + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { 43 + $where = array(); 44 + 45 + if ($this->phids) { 46 + $where[] = qsprintf( 47 + $conn_r, 48 + 'phid IN (%Ls)', 49 + $this->phids); 50 + } 51 + 52 + if ($this->transactionPHIDs) { 53 + $where[] = qsprintf( 54 + $conn_r, 55 + 'transactionPHID IN (%Ls)', 56 + $this->transactionPHIDs); 57 + } 58 + 59 + return $this->formatWhereClause($where); 60 + } 61 + 62 + }
+143
src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorApplicationTransactionQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $phids; 7 + private $objectPHIDs; 8 + private $authorPHIDs; 9 + 10 + private $needComments = true; 11 + private $needHandles = true; 12 + 13 + abstract protected function getTemplateApplicationTransaction(); 14 + 15 + protected function buildMoreWhereClauses(AphrontDatabaseConnection $conn_r) { 16 + return array(); 17 + } 18 + 19 + protected function getReversePaging() { 20 + return true; 21 + } 22 + 23 + public function withPHIDs(array $phids) { 24 + $this->phids = $phids; 25 + return $this; 26 + } 27 + 28 + public function withObjectPHIDs(array $object_phids) { 29 + $this->objectPHIDs = $object_phids; 30 + return $this; 31 + } 32 + 33 + public function withAuthorPHIDs(array $author_phids) { 34 + $this->authorPHIDs = $author_phids; 35 + return $this; 36 + } 37 + 38 + public function needComments($need) { 39 + $this->needComments = $need; 40 + return $this; 41 + } 42 + 43 + public function needHandles($need) { 44 + $this->needHandles = $need; 45 + return $this; 46 + } 47 + 48 + public function loadPage() { 49 + $table = $this->getTemplateApplicationTransaction(); 50 + $conn_r = $table->establishConnection('r'); 51 + 52 + $data = queryfx_all( 53 + $conn_r, 54 + 'SELECT * FROM %T x %Q %Q %Q', 55 + $table->getTableName(), 56 + $this->buildWhereClause($conn_r), 57 + $this->buildOrderClause($conn_r), 58 + $this->buildLimitClause($conn_r)); 59 + 60 + $xactions = $table->loadAllFromArray($data); 61 + 62 + if ($this->needComments) { 63 + $comment_phids = array_filter(mpull($xactions, 'getCommentPHID')); 64 + 65 + $comments = array(); 66 + if ($comment_phids) { 67 + $comments = id(new PhabricatorApplicationTransactionCommentQuery()) 68 + ->setTemplate($table->getApplicationTransactionCommentObject()) 69 + ->setViewer($this->getViewer()) 70 + ->withPHIDs($comment_phids) 71 + ->execute(); 72 + $comments = mpull($comments, null, 'getPHID'); 73 + } 74 + 75 + foreach ($xactions as $xaction) { 76 + if ($xaction->getCommentPHID()) { 77 + $comment = idx($comments, $xaction->getCommentPHID()); 78 + if ($comment) { 79 + $xaction->attachComment($comment); 80 + } 81 + } 82 + } 83 + } else { 84 + foreach ($xactions as $xaction) { 85 + $xaction->setCommentNotLoaded(true); 86 + } 87 + } 88 + 89 + if ($this->needHandles) { 90 + $phids = array(); 91 + foreach ($xactions as $xaction) { 92 + $phids[$xaction->getPHID()] = $xaction->getRequiredHandlePHIDs(); 93 + } 94 + $handles = array(); 95 + $merged = array_mergev($phids); 96 + if ($merged) { 97 + $handles = id(new PhabricatorObjectHandleData($merged)) 98 + ->setViewer($this->getViewer()) 99 + ->loadHandles(); 100 + } 101 + foreach ($xactions as $xaction) { 102 + $xaction->setHandles( 103 + array_select_keys( 104 + $handles, 105 + $phids[$xaction->getPHID()])); 106 + } 107 + } 108 + 109 + return $xactions; 110 + } 111 + 112 + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { 113 + $where = array(); 114 + 115 + if ($this->phids) { 116 + $where[] = qsprintf( 117 + $conn_r, 118 + 'phid IN (%Ls)', 119 + $this->phids); 120 + } 121 + 122 + if ($this->objectPHIDs) { 123 + $where[] = qsprintf( 124 + $conn_r, 125 + 'objectPHID IN (%Ls)', 126 + $this->objectPHIDs); 127 + } 128 + 129 + if ($this->authorPHIDs) { 130 + $where[] = qsprintf( 131 + $conn_r, 132 + 'authorPHID IN (%Ls)', 133 + $this->authorPHIDs); 134 + } 135 + 136 + foreach ($this->buildMoreWhereClauses($conn_r) as $clause) { 137 + $where[] = $clause; 138 + } 139 + 140 + return $this->formatWhereClause($where); 141 + } 142 + 143 + }
+222
src/applications/transactions/storage/PhabricatorApplicationTransaction.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorApplicationTransaction 4 + extends PhabricatorLiskDAO 5 + implements PhabricatorPolicyInterface{ 6 + 7 + const MARKUP_FIELD_COMMENT = 'markup:comment'; 8 + 9 + protected $phid; 10 + protected $objectPHID; 11 + protected $authorPHID; 12 + protected $viewPolicy; 13 + protected $editPolicy; 14 + 15 + protected $commentPHID; 16 + protected $commentVersion = 0; 17 + 18 + protected $transactionType; 19 + protected $oldValue; 20 + protected $newValue; 21 + 22 + protected $contentSource; 23 + 24 + private $comment; 25 + private $commentNotLoaded; 26 + 27 + private $handles; 28 + 29 + abstract public function getApplicationTransactionType(); 30 + abstract public function getApplicationTransactionCommentObject(); 31 + abstract public function getApplicationObjectTypeName(); 32 + 33 + public function generatePHID() { 34 + $type = PhabricatorPHIDConstants::PHID_TYPE_XACT; 35 + $subtype = $this->getApplicationTransactionType(); 36 + 37 + return PhabricatorPHID::generateNewPHID($type, $subtype); 38 + } 39 + 40 + public function getConfiguration() { 41 + return array( 42 + self::CONFIG_AUX_PHID => true, 43 + self::CONFIG_SERIALIZATION => array( 44 + 'oldValue' => self::SERIALIZATION_JSON, 45 + 'newValue' => self::SERIALIZATION_JSON, 46 + ), 47 + ) + parent::getConfiguration(); 48 + } 49 + 50 + public function setContentSource(PhabricatorContentSource $content_source) { 51 + $this->contentSource = $content_source->serialize(); 52 + return $this; 53 + } 54 + 55 + public function getContentSource() { 56 + return PhabricatorContentSource::newFromSerialized($this->contentSource); 57 + } 58 + 59 + public function getComment() { 60 + if ($this->commentNotLoaded) { 61 + throw new Exception("Comment for this transaction was not loaded."); 62 + } 63 + return $this->comment; 64 + } 65 + 66 + public function attachComment( 67 + PhabricatorApplicationTransactionComment $comment) { 68 + $this->comment = $comment; 69 + $this->commentNotLoaded = false; 70 + return $this; 71 + } 72 + 73 + public function setCommentNotLoaded($not_loaded) { 74 + $this->commentNotLoaded = $not_loaded; 75 + return $this; 76 + } 77 + 78 + /* -( Rendering )---------------------------------------------------------- */ 79 + 80 + public function getRequiredHandlePHIDs() { 81 + $phids = array(); 82 + 83 + $old = $this->getOldValue(); 84 + $new = $this->getNewValue(); 85 + 86 + $phids[] = array($this->getAuthorPHID()); 87 + switch ($this->getTransactionType()) { 88 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 89 + $phids[] = $old; 90 + $phids[] = $new; 91 + break; 92 + } 93 + 94 + return array_mergev($phids); 95 + } 96 + 97 + public function setHandles(array $handles) { 98 + $this->handles = $handles; 99 + return $this; 100 + } 101 + 102 + public function getHandle($phid) { 103 + if (empty($this->handles[$phid])) { 104 + throw new Exception( 105 + "Transaction requires a handle ('{$phid}') it did not load."); 106 + } 107 + return $this->handles[$phid]; 108 + } 109 + 110 + protected function renderHandleLink($phid) { 111 + return $this->getHandle($phid)->renderLink(); 112 + } 113 + 114 + protected function renderHandleList(array $phids) { 115 + $links = array(); 116 + foreach ($phids as $phid) { 117 + $links[] = $this->renderHandleLink($phid); 118 + } 119 + return implode(', ', $links); 120 + } 121 + 122 + public function shouldHide() { 123 + switch ($this->getTransactionType()) { 124 + case PhabricatorTransactions::TYPE_VIEW_POLICY: 125 + case PhabricatorTransactions::TYPE_EDIT_POLICY: 126 + if ($this->getOldValue() === null) { 127 + return true; 128 + } else { 129 + return false; 130 + } 131 + break; 132 + } 133 + 134 + return false; 135 + } 136 + 137 + public function getTitle() { 138 + $author_phid = $this->getAuthorPHID(); 139 + 140 + $old = $this->getOldValue(); 141 + $new = $this->getNewValue(); 142 + 143 + switch ($this->getTransactionType()) { 144 + case PhabricatorTransactions::TYPE_COMMENT: 145 + return pht( 146 + '%s added a comment.', 147 + $this->renderHandleLink($author_phid)); 148 + case PhabricatorTransactions::TYPE_VIEW_POLICY: 149 + // TODO: Render human-readable. 150 + return pht( 151 + '%s changed the visibility of this %s from "%s" to "%s".', 152 + $this->renderHandleLink($author_phid), 153 + $this->getApplicationObjectTypeName(), 154 + phutil_escape_html($old), 155 + phutil_escape_html($new)); 156 + case PhabricatorTransactions::TYPE_EDIT_POLICY: 157 + // TODO: Render human-readable. 158 + return pht( 159 + '%s changed the edit policy of this %s from "%s" to "%s".', 160 + $this->renderHandleLink($author_phid), 161 + $this->getApplicationObjectTypeName(), 162 + phutil_escape_html($old), 163 + phutil_escape_html($new)); 164 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 165 + $add = array_diff($new, $old); 166 + $rem = array_diff($old, $new); 167 + 168 + if ($add && $rem) { 169 + return pht( 170 + '%s edited subscriber(s), added %d: %s; removed %d: %s.', 171 + $this->renderHandleLink($author_phid), 172 + count($add), 173 + $this->renderHandleList($add), 174 + count($rem), 175 + $this->renderHandleList($rem)); 176 + } else if ($add) { 177 + return pht( 178 + '%s added %d subscriber(s): %s.', 179 + $this->renderHandleLink($author_phid), 180 + count($add), 181 + $this->renderHandleList($add)); 182 + } else { 183 + return pht( 184 + '%s removed %d subscribers: %s.', 185 + $this->renderHandleLink($author_phid), 186 + count($rem), 187 + $this->renderHandleList($rem)); 188 + } 189 + break; 190 + default: 191 + return pht( 192 + '%s edited this %s.', 193 + $this->renderHandleLink($author_phid), 194 + $this->getApplicationObjectTypeName()); 195 + } 196 + } 197 + 198 + 199 + /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ 200 + 201 + 202 + public function getCapabilities() { 203 + return array( 204 + PhabricatorPolicyCapability::CAN_VIEW, 205 + PhabricatorPolicyCapability::CAN_EDIT, 206 + ); 207 + } 208 + 209 + public function getPolicy($capability) { 210 + switch ($capability) { 211 + case PhabricatorPolicyCapability::CAN_VIEW: 212 + return $this->getViewPolicy(); 213 + case PhabricatorPolicyCapability::CAN_EDIT: 214 + return $this->getEditPolicy(); 215 + } 216 + } 217 + 218 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 219 + return ($viewer->getPHID() == $this->getAuthorPHID()); 220 + } 221 + 222 + }
+103
src/applications/transactions/storage/PhabricatorApplicationTransactionComment.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorApplicationTransactionComment 4 + extends PhabricatorLiskDAO 5 + implements PhabricatorMarkupInterface, PhabricatorPolicyInterface { 6 + 7 + protected $transactionPHID; 8 + protected $commentVersion; 9 + protected $authorPHID; 10 + protected $viewPolicy; 11 + protected $editPolicy; 12 + protected $content; 13 + protected $contentSource; 14 + protected $isDeleted = 0; 15 + 16 + abstract public function getApplicationTransactionObject(); 17 + 18 + public function generatePHID() { 19 + return PhabricatorPHID::generateNewPHID( 20 + PhabricatorPHIDConstants::PHID_TYPE_XCMT); 21 + } 22 + 23 + public function getConfiguration() { 24 + return array( 25 + self::CONFIG_AUX_PHID => true, 26 + ) + parent::getConfiguration(); 27 + } 28 + 29 + public function getApplicationName() { 30 + return $this->getApplicationTransactionObject()->getApplicationName(); 31 + } 32 + 33 + public function getTableName() { 34 + $xaction = $this->getApplicationTransactionObject(); 35 + return self::getTableNameFromTransaction($xaction); 36 + } 37 + 38 + public static function getTableNameFromTransaction( 39 + PhabricatorApplicationTransaction $xaction) { 40 + return $xaction->getTableName().'_comment'; 41 + } 42 + 43 + public function setContentSource(PhabricatorContentSource $content_source) { 44 + $this->contentSource = $content_source->serialize(); 45 + return $this; 46 + } 47 + 48 + public function getContentSource() { 49 + return PhabricatorContentSource::newFromSerialized($this->contentSource); 50 + } 51 + 52 + 53 + /* -( PhabricatorMarkupInterface )----------------------------------------- */ 54 + 55 + 56 + public function getMarkupFieldKey($field) { 57 + return PhabricatorPHIDConstants::PHID_TYPE_XCMT.':'.$this->getPHID(); 58 + } 59 + 60 + 61 + public function newMarkupEngine($field) { 62 + return PhabricatorMarkupEngine::newMarkupEngine(array()); 63 + } 64 + 65 + 66 + public function getMarkupText($field) { 67 + return $this->getContent(); 68 + } 69 + 70 + 71 + public function didMarkupText($field, $output, PhutilMarkupEngine $engine) { 72 + return $output; 73 + } 74 + 75 + 76 + public function shouldUseMarkupCache($field) { 77 + return (bool)$this->getPHID(); 78 + } 79 + 80 + /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ 81 + 82 + 83 + public function getCapabilities() { 84 + return array( 85 + PhabricatorPolicyCapability::CAN_VIEW, 86 + PhabricatorPolicyCapability::CAN_EDIT, 87 + ); 88 + } 89 + 90 + public function getPolicy($capability) { 91 + switch ($capability) { 92 + case PhabricatorPolicyCapability::CAN_VIEW: 93 + return $this->getViewPolicy(); 94 + case PhabricatorPolicyCapability::CAN_EDIT: 95 + return $this->getEditPolicy(); 96 + } 97 + } 98 + 99 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 100 + return ($viewer->getPHID() == $this->getAuthorPHID()); 101 + } 102 + 103 + }
+4
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1044 1044 'type' => 'sql', 1045 1045 'name' => $this->getPatchPath('owners-exclude.sql'), 1046 1046 ), 1047 + '20121209.pholioxactions.sql' => array( 1048 + 'type' => 'sql', 1049 + 'name' => $this->getPatchPath('20121209.pholioxactions.sql'), 1050 + ), 1047 1051 ); 1048 1052 } 1049 1053