@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

Scaffolding for Fund

Summary:
Ref T5835. This is all pretty boilerplate, and does not interact with Phortune at all yet.

You can create "Initiatives", which have a title and description, and support most of the expected infrastructure (policies, transactions, mentions, edges, appsearch, remakrup, etc).

Only notable decisions:

- Initiatives have an explicit owner. I think it's good to have a single clearly-responsible user behind an initiative.
- I think that's it?

Test Plan:
- Created an initiative.
- Edited an initiative.
- Changed application policy defaults.
- Searched for initiatives.
- Subscribed to an initiative.
- Opened/closed an initiative.
- Used `I123` and `{I123}` in remarkup.
- Destroyed an initiative.

Reviewers: chad, btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T5835

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

+1722
+15
resources/sql/autopatches/20140911.fund.1.initiative.sql
··· 1 + CREATE TABLE {$NAMESPACE}_fund.fund_initiative ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + name VARCHAR(255) NOT NULL, 5 + ownerPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 6 + description LONGTEXT NOT NULL, 7 + viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 8 + editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 9 + status VARCHAR(32) NOT NULL COLLATE utf8_bin, 10 + dateCreated INT UNSIGNED NOT NULL, 11 + dateModified INT UNSIGNED NOT NULL, 12 + UNIQUE KEY `key_phid` (phid), 13 + KEY `key_status` (status), 14 + KEY `key_owner` (ownerPHID) 15 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+19
resources/sql/autopatches/20140911.fund.2.xaction.sql
··· 1 + CREATE TABLE {$NAMESPACE}_fund.fund_initiativetransaction ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARCHAR(64) COLLATE utf8_bin NOT NULL, 4 + authorPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, 5 + objectPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, 6 + viewPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, 7 + editPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, 8 + commentPHID VARCHAR(64) COLLATE utf8_bin DEFAULT NULL, 9 + commentVersion INT UNSIGNED NOT NULL, 10 + transactionType VARCHAR(32) COLLATE utf8_bin NOT NULL, 11 + oldValue LONGTEXT COLLATE utf8_bin NOT NULL, 12 + newValue LONGTEXT COLLATE utf8_bin NOT NULL, 13 + contentSource LONGTEXT COLLATE utf8_bin NOT NULL, 14 + metadata LONGTEXT COLLATE utf8_bin NOT NULL, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + UNIQUE KEY `key_phid` (`phid`), 18 + KEY `key_object` (`objectPHID`) 19 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+15
resources/sql/autopatches/20140911.fund.3.edge.sql
··· 1 + CREATE TABLE {$NAMESPACE}_fund.edge ( 2 + src VARCHAR(64) NOT NULL COLLATE utf8_bin, 3 + type VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + dst VARCHAR(64) NOT NULL COLLATE utf8_bin, 5 + dateCreated INT UNSIGNED NOT NULL, 6 + seq INT UNSIGNED NOT NULL, 7 + dataID INT UNSIGNED, 8 + PRIMARY KEY (src, type, dst), 9 + KEY (src, type, dateCreated, seq) 10 + ) ENGINE=InnoDB, COLLATE utf8_general_ci; 11 + 12 + CREATE TABLE {$NAMESPACE}_fund.edgedata ( 13 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 14 + data LONGTEXT NOT NULL COLLATE utf8_bin 15 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+60
src/__phutil_library_map__.php
··· 654 654 'FlagDeleteConduitAPIMethod' => 'applications/flag/conduit/FlagDeleteConduitAPIMethod.php', 655 655 'FlagEditConduitAPIMethod' => 'applications/flag/conduit/FlagEditConduitAPIMethod.php', 656 656 'FlagQueryConduitAPIMethod' => 'applications/flag/conduit/FlagQueryConduitAPIMethod.php', 657 + 'FundBacking' => 'applications/fund/storage/FundBacking.php', 658 + 'FundBackingEditor' => 'applications/fund/editor/FundBackingEditor.php', 659 + 'FundBackingPHIDType' => 'applications/fund/phid/FundBackingPHIDType.php', 660 + 'FundBackingQuery' => 'applications/fund/query/FundBackingQuery.php', 661 + 'FundBackingTransaction' => 'applications/fund/storage/FundBackingTransaction.php', 662 + 'FundBackingTransactionQuery' => 'applications/fund/query/FundBackingTransactionQuery.php', 663 + 'FundController' => 'applications/fund/controller/FundController.php', 664 + 'FundCreateInitiativesCapability' => 'applications/fund/capability/FundCreateInitiativesCapability.php', 665 + 'FundDAO' => 'applications/fund/storage/FundDAO.php', 666 + 'FundDefaultViewCapability' => 'applications/fund/capability/FundDefaultViewCapability.php', 667 + 'FundInitiative' => 'applications/fund/storage/FundInitiative.php', 668 + 'FundInitiativeCloseController' => 'applications/fund/controller/FundInitiativeCloseController.php', 669 + 'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php', 670 + 'FundInitiativeEditor' => 'applications/fund/editor/FundInitiativeEditor.php', 671 + 'FundInitiativeListController' => 'applications/fund/controller/FundInitiativeListController.php', 672 + 'FundInitiativePHIDType' => 'applications/fund/phid/FundInitiativePHIDType.php', 673 + 'FundInitiativeQuery' => 'applications/fund/query/FundInitiativeQuery.php', 674 + 'FundInitiativeRemarkupRule' => 'applications/fund/remarkup/FundInitiativeRemarkupRule.php', 675 + 'FundInitiativeSearchEngine' => 'applications/fund/query/FundInitiativeSearchEngine.php', 676 + 'FundInitiativeTransaction' => 'applications/fund/storage/FundInitiativeTransaction.php', 677 + 'FundInitiativeTransactionQuery' => 'applications/fund/query/FundInitiativeTransactionQuery.php', 678 + 'FundInitiativeViewController' => 'applications/fund/controller/FundInitiativeViewController.php', 657 679 'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php', 658 680 'HarbormasterBuildAbortedException' => 'applications/harbormaster/exception/HarbormasterBuildAbortedException.php', 659 681 'HarbormasterBuildActionController' => 'applications/harbormaster/controller/HarbormasterBuildActionController.php', ··· 1600 1622 'PhabricatorFlagsApplication' => 'applications/flag/application/PhabricatorFlagsApplication.php', 1601 1623 'PhabricatorFlagsUIEventListener' => 'applications/flag/events/PhabricatorFlagsUIEventListener.php', 1602 1624 'PhabricatorFormExample' => 'applications/uiexample/examples/PhabricatorFormExample.php', 1625 + 'PhabricatorFundApplication' => 'applications/fund/application/PhabricatorFundApplication.php', 1603 1626 'PhabricatorGarbageCollector' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php', 1604 1627 'PhabricatorGarbageCollectorConfigOptions' => 'applications/config/option/PhabricatorGarbageCollectorConfigOptions.php', 1605 1628 'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollectorDaemon.php', ··· 3422 3445 'FlagDeleteConduitAPIMethod' => 'FlagConduitAPIMethod', 3423 3446 'FlagEditConduitAPIMethod' => 'FlagConduitAPIMethod', 3424 3447 'FlagQueryConduitAPIMethod' => 'FlagConduitAPIMethod', 3448 + 'FundBacking' => array( 3449 + 'FundDAO', 3450 + 'PhabricatorPolicyInterface', 3451 + 'PhabricatorApplicationTransactionInterface', 3452 + ), 3453 + 'FundBackingEditor' => 'PhabricatorApplicationTransactionEditor', 3454 + 'FundBackingPHIDType' => 'PhabricatorPHIDType', 3455 + 'FundBackingQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3456 + 'FundBackingTransaction' => 'PhabricatorApplicationTransaction', 3457 + 'FundBackingTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3458 + 'FundController' => 'PhabricatorController', 3459 + 'FundCreateInitiativesCapability' => 'PhabricatorPolicyCapability', 3460 + 'FundDAO' => 'PhabricatorLiskDAO', 3461 + 'FundDefaultViewCapability' => 'PhabricatorPolicyCapability', 3462 + 'FundInitiative' => array( 3463 + 'FundDAO', 3464 + 'PhabricatorPolicyInterface', 3465 + 'PhabricatorProjectInterface', 3466 + 'PhabricatorApplicationTransactionInterface', 3467 + 'PhabricatorSubscribableInterface', 3468 + 'PhabricatorMentionableInterface', 3469 + 'PhabricatorFlaggableInterface', 3470 + 'PhabricatorTokenReceiverInterface', 3471 + 'PhabricatorDestructibleInterface', 3472 + ), 3473 + 'FundInitiativeCloseController' => 'FundController', 3474 + 'FundInitiativeEditController' => 'FundController', 3475 + 'FundInitiativeEditor' => 'PhabricatorApplicationTransactionEditor', 3476 + 'FundInitiativeListController' => 'FundController', 3477 + 'FundInitiativePHIDType' => 'PhabricatorPHIDType', 3478 + 'FundInitiativeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3479 + 'FundInitiativeRemarkupRule' => 'PhabricatorObjectRemarkupRule', 3480 + 'FundInitiativeSearchEngine' => 'PhabricatorApplicationSearchEngine', 3481 + 'FundInitiativeTransaction' => 'PhabricatorApplicationTransaction', 3482 + 'FundInitiativeTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3483 + 'FundInitiativeViewController' => 'FundController', 3425 3484 'HarbormasterBuild' => array( 3426 3485 'HarbormasterDAO', 3427 3486 'PhabricatorPolicyInterface', ··· 4465 4524 'PhabricatorFlagsApplication' => 'PhabricatorApplication', 4466 4525 'PhabricatorFlagsUIEventListener' => 'PhabricatorEventListener', 4467 4526 'PhabricatorFormExample' => 'PhabricatorUIExample', 4527 + 'PhabricatorFundApplication' => 'PhabricatorApplication', 4468 4528 'PhabricatorGarbageCollector' => 'Phobject', 4469 4529 'PhabricatorGarbageCollectorConfigOptions' => 'PhabricatorApplicationConfigOptions', 4470 4530 'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon',
+62
src/applications/fund/application/PhabricatorFundApplication.php
··· 1 + <?php 2 + 3 + final class PhabricatorFundApplication extends PhabricatorApplication { 4 + 5 + public function getName() { 6 + return pht('Fund'); 7 + } 8 + 9 + public function getBaseURI() { 10 + return '/fund/'; 11 + } 12 + 13 + public function getShortDescription() { 14 + return pht('Donate'); 15 + } 16 + 17 + public function getIconName() { 18 + return 'phund'; 19 + } 20 + 21 + public function getTitleGlyph() { 22 + return "\xE2\x99\xA5"; 23 + } 24 + 25 + public function getApplicationGroup() { 26 + return self::GROUP_UTILITIES; 27 + } 28 + 29 + public function isBeta() { 30 + return true; 31 + } 32 + 33 + public function getRemarkupRules() { 34 + return array( 35 + new FundInitiativeRemarkupRule(), 36 + ); 37 + } 38 + 39 + public function getRoutes() { 40 + return array( 41 + '/I(?P<id>[1-9]\d*)' => 'FundInitiativeViewController', 42 + '/fund/' => array( 43 + '(?:query/(?P<queryKey>[^/]+)/)?' => 'FundInitiativeListController', 44 + 'create/' => 'FundInitiativeEditController', 45 + 'edit/(?:(?P<id>[^/]+)/)?' => 'FundInitiativeEditController', 46 + 'close/(?P<id>[^/]+)/' => 'FundInitiativeCloseController', 47 + ), 48 + ); 49 + } 50 + 51 + protected function getCustomCapabilities() { 52 + return array( 53 + FundDefaultViewCapability::CAPABILITY => array( 54 + 'caption' => pht('Default view policy for newly created initiatives.'), 55 + ), 56 + FundCreateInitiativesCapability::CAPABILITY => array( 57 + 'default' => PhabricatorPolicies::POLICY_ADMIN, 58 + ), 59 + ); 60 + } 61 + 62 + }
+16
src/applications/fund/capability/FundCreateInitiativesCapability.php
··· 1 + <?php 2 + 3 + final class FundCreateInitiativesCapability 4 + extends PhabricatorPolicyCapability { 5 + 6 + const CAPABILITY = 'fund.create'; 7 + 8 + public function getCapabilityName() { 9 + return pht('Can Create Initiatives'); 10 + } 11 + 12 + public function describeCapabilityRejection() { 13 + return pht('You do not have permission to create Fund initiatives.'); 14 + } 15 + 16 + }
+15
src/applications/fund/capability/FundDefaultViewCapability.php
··· 1 + <?php 2 + 3 + final class FundDefaultViewCapability extends PhabricatorPolicyCapability { 4 + 5 + const CAPABILITY = 'fund.default.view'; 6 + 7 + public function getCapabilityName() { 8 + return pht('Default View Policy'); 9 + } 10 + 11 + public function shouldAllowPublicPolicySetting() { 12 + return true; 13 + } 14 + 15 + }
+3
src/applications/fund/controller/FundController.php
··· 1 + <?php 2 + 3 + abstract class FundController extends PhabricatorController {}
+73
src/applications/fund/controller/FundInitiativeCloseController.php
··· 1 + <?php 2 + 3 + final class FundInitiativeCloseController 4 + extends FundController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = $data['id']; 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $initiative = id(new FundInitiativeQuery()) 17 + ->setViewer($viewer) 18 + ->withIDs(array($this->id)) 19 + ->requireCapabilities( 20 + array( 21 + PhabricatorPolicyCapability::CAN_VIEW, 22 + PhabricatorPolicyCapability::CAN_EDIT, 23 + )) 24 + ->executeOne(); 25 + if (!$initiative) { 26 + return new Aphront404Response(); 27 + } 28 + 29 + $initiative_uri = '/'.$initiative->getMonogram(); 30 + 31 + $is_close = !$initiative->isClosed(); 32 + 33 + if ($request->isFormPost()) { 34 + $type_status = FundInitiativeTransaction::TYPE_STATUS; 35 + 36 + if ($is_close) { 37 + $new_status = FundInitiative::STATUS_CLOSED; 38 + } else { 39 + $new_status = FundInitiative::STATUS_OPEN; 40 + } 41 + 42 + $xaction = id(new FundInitiativeTransaction()) 43 + ->setTransactionType($type_status) 44 + ->setNewValue($new_status); 45 + 46 + $editor = id(new FundInitiativeEditor()) 47 + ->setActor($viewer) 48 + ->setContentSourceFromRequest($request) 49 + ->setContinueOnMissingFields(true); 50 + 51 + $editor->applyTransactions($initiative, array($xaction)); 52 + 53 + return id(new AphrontRedirectResponse())->setURI($initiative_uri); 54 + } 55 + 56 + if ($is_close) { 57 + $title = pht('Close Initiative?'); 58 + $body = pht('Really close this initiative?'); 59 + $button_text = pht('Close Initiative'); 60 + } else { 61 + $title = pht('Reopen Initiative?'); 62 + $body = pht('Really reopen this initiative?'); 63 + $button_text = pht('Reopen Initiative'); 64 + } 65 + 66 + return $this->newDialog() 67 + ->setTitle($title) 68 + ->appendParagraph($body) 69 + ->addCancelButton($initiative_uri) 70 + ->addSubmitButton($button_text); 71 + } 72 + 73 + }
+191
src/applications/fund/controller/FundInitiativeEditController.php
··· 1 + <?php 2 + 3 + final class FundInitiativeEditController 4 + extends FundController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = idx($data, 'id'); 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + if ($this->id) { 17 + $initiative = id(new FundInitiativeQuery()) 18 + ->setViewer($viewer) 19 + ->withIDs(array($this->id)) 20 + ->requireCapabilities( 21 + array( 22 + PhabricatorPolicyCapability::CAN_VIEW, 23 + PhabricatorPolicyCapability::CAN_EDIT, 24 + )) 25 + ->executeOne(); 26 + if (!$initiative) { 27 + return new Aphront404Response(); 28 + } 29 + $is_new = false; 30 + } else { 31 + $initiative = FundInitiative::initializeNewInitiative($viewer); 32 + $is_new = true; 33 + } 34 + 35 + if ($is_new) { 36 + $title = pht('Create Initiative'); 37 + $button_text = pht('Create Initiative'); 38 + $cancel_uri = $this->getApplicationURI(); 39 + } else { 40 + $title = pht( 41 + 'Edit %s %s', 42 + $initiative->getMonogram(), 43 + $initiative->getName()); 44 + $button_text = pht('Save Changes'); 45 + $cancel_uri = '/'.$initiative->getMonogram(); 46 + } 47 + 48 + $e_name = true; 49 + $v_name = $initiative->getName(); 50 + 51 + $v_desc = $initiative->getDescription(); 52 + 53 + if ($is_new) { 54 + $v_projects = array(); 55 + } else { 56 + $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( 57 + $initiative->getPHID(), 58 + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); 59 + $v_projects = array_reverse($v_projects); 60 + } 61 + 62 + $validation_exception = null; 63 + if ($request->isFormPost()) { 64 + $v_name = $request->getStr('name'); 65 + $v_desc = $request->getStr('description'); 66 + $v_view = $request->getStr('viewPolicy'); 67 + $v_edit = $request->getStr('editPolicy'); 68 + $v_projects = $request->getArr('projects'); 69 + 70 + $type_name = FundInitiativeTransaction::TYPE_NAME; 71 + $type_desc = FundInitiativeTransaction::TYPE_DESCRIPTION; 72 + $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; 73 + $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; 74 + 75 + $xactions = array(); 76 + 77 + $xactions[] = id(new FundInitiativeTransaction()) 78 + ->setTransactionType($type_name) 79 + ->setNewValue($v_name); 80 + 81 + $xactions[] = id(new FundInitiativeTransaction()) 82 + ->setTransactionType($type_desc) 83 + ->setNewValue($v_desc); 84 + 85 + $xactions[] = id(new FundInitiativeTransaction()) 86 + ->setTransactionType($type_view) 87 + ->setNewValue($v_view); 88 + 89 + $xactions[] = id(new FundInitiativeTransaction()) 90 + ->setTransactionType($type_edit) 91 + ->setNewValue($v_edit); 92 + 93 + $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 94 + $xactions[] = id(new FundInitiativeTransaction()) 95 + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 96 + ->setMetadataValue('edge:type', $proj_edge_type) 97 + ->setNewValue(array('=' => array_fuse($v_projects))); 98 + 99 + $editor = id(new FundInitiativeEditor()) 100 + ->setActor($viewer) 101 + ->setContentSourceFromRequest($request) 102 + ->setContinueOnNoEffect(true); 103 + 104 + try { 105 + $editor->applyTransactions($initiative, $xactions); 106 + 107 + return id(new AphrontRedirectResponse()) 108 + ->setURI('/'.$initiative->getMonogram()); 109 + } catch (PhabricatorApplicationTransactionValidationException $ex) { 110 + $validation_exception = $ex; 111 + 112 + $e_name = $ex->getShortMessage($type_name); 113 + 114 + $initiative->setViewPolicy($v_view); 115 + $initiative->setEditPolicy($v_edit); 116 + } 117 + } 118 + 119 + $policies = id(new PhabricatorPolicyQuery()) 120 + ->setViewer($viewer) 121 + ->setObject($initiative) 122 + ->execute(); 123 + 124 + if ($v_projects) { 125 + $project_handles = $this->loadViewerHandles($v_projects); 126 + } else { 127 + $project_handles = array(); 128 + } 129 + 130 + $form = id(new AphrontFormView()) 131 + ->setUser($viewer) 132 + ->appendChild( 133 + id(new AphrontFormTextControl()) 134 + ->setName('name') 135 + ->setLabel(pht('Name')) 136 + ->setValue($v_name) 137 + ->setError($e_name)) 138 + ->appendChild( 139 + id(new PhabricatorRemarkupControl()) 140 + ->setName('description') 141 + ->setLabel(pht('Description')) 142 + ->setValue($v_desc)) 143 + ->appendChild( 144 + id(new AphrontFormTokenizerControl()) 145 + ->setLabel(pht('Projects')) 146 + ->setName('projects') 147 + ->setValue($project_handles) 148 + ->setDatasource(new PhabricatorProjectDatasource())) 149 + ->appendChild( 150 + id(new AphrontFormPolicyControl()) 151 + ->setName('viewPolicy') 152 + ->setPolicyObject($initiative) 153 + ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) 154 + ->setPolicies($policies)) 155 + ->appendChild( 156 + id(new AphrontFormPolicyControl()) 157 + ->setName('editPolicy') 158 + ->setPolicyObject($initiative) 159 + ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) 160 + ->setPolicies($policies)) 161 + ->appendChild( 162 + id(new AphrontFormSubmitControl()) 163 + ->setValue($button_text) 164 + ->addCancelButton($cancel_uri)); 165 + 166 + $crumbs = $this->buildApplicationCrumbs(); 167 + if ($is_new) { 168 + $crumbs->addTextCrumb(pht('Create Initiative')); 169 + } else { 170 + $crumbs->addTextCrumb( 171 + $initiative->getMonogram(), 172 + '/'.$initiative->getMonogram()); 173 + $crumbs->addTextCrumb(pht('Edit')); 174 + } 175 + 176 + $box = id(new PHUIObjectBoxView()) 177 + ->setValidationException($validation_exception) 178 + ->setHeaderText($title) 179 + ->appendChild($form); 180 + 181 + return $this->buildApplicationPage( 182 + array( 183 + $crumbs, 184 + $box, 185 + ), 186 + array( 187 + 'title' => $title, 188 + )); 189 + } 190 + 191 + }
+53
src/applications/fund/controller/FundInitiativeListController.php
··· 1 + <?php 2 + 3 + final class FundInitiativeListController 4 + extends FundController { 5 + 6 + private $queryKey; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->queryKey = idx($data, 'queryKey'); 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $controller = id(new PhabricatorApplicationSearchController($request)) 15 + ->setQueryKey($this->queryKey) 16 + ->setSearchEngine(new FundInitiativeSearchEngine()) 17 + ->setNavigation($this->buildSideNavView()); 18 + 19 + return $this->delegateToController($controller); 20 + } 21 + 22 + public function buildSideNavView() { 23 + $user = $this->getRequest()->getUser(); 24 + 25 + $nav = new AphrontSideNavFilterView(); 26 + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 27 + 28 + id(new FundInitiativeSearchEngine()) 29 + ->setViewer($user) 30 + ->addNavigationItems($nav->getMenu()); 31 + $nav->selectFilter(null); 32 + 33 + return $nav; 34 + } 35 + 36 + public function buildApplicationCrumbs() { 37 + $crumbs = parent::buildApplicationCrumbs(); 38 + 39 + $can_create = $this->hasApplicationCapability( 40 + FundCreateInitiativesCapability::CAPABILITY); 41 + 42 + $crumbs->addAction( 43 + id(new PHUIListItemView()) 44 + ->setName(pht('Create Initiative')) 45 + ->setHref($this->getApplicationURI('create/')) 46 + ->setIcon('fa-plus-square') 47 + ->setDisabled(!$can_create) 48 + ->setWorkflow(!$can_create)); 49 + 50 + return $crumbs; 51 + } 52 + 53 + }
+149
src/applications/fund/controller/FundInitiativeViewController.php
··· 1 + <?php 2 + 3 + final class FundInitiativeViewController 4 + extends FundController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = $data['id']; 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $initiative = id(new FundInitiativeQuery()) 17 + ->setViewer($viewer) 18 + ->withIDs(array($this->id)) 19 + ->executeOne(); 20 + if (!$initiative) { 21 + return new Aphront404Response(); 22 + } 23 + 24 + $crumbs = $this->buildApplicationCrumbs(); 25 + $crumbs->addTextCrumb($initiative->getMonogram()); 26 + 27 + $title = pht( 28 + '%s %s', 29 + $initiative->getMonogram(), 30 + $initiative->getName()); 31 + 32 + if ($initiative->isClosed()) { 33 + $status_icon = 'fa-times'; 34 + $status_color = 'bluegrey'; 35 + } else { 36 + $status_icon = 'fa-check'; 37 + $status_color = 'bluegrey'; 38 + } 39 + $status_name = idx( 40 + FundInitiative::getStatusNameMap(), 41 + $initiative->getStatus()); 42 + 43 + $header = id(new PHUIHeaderView()) 44 + ->setObjectName($initiative->getMonogram()) 45 + ->setHeader($initiative->getName()) 46 + ->setUser($viewer) 47 + ->setPolicyObject($initiative) 48 + ->setStatus($status_icon, $status_color, $status_name); 49 + 50 + $properties = $this->buildPropertyListView($initiative); 51 + $actions = $this->buildActionListView($initiative); 52 + $properties->setActionList($actions); 53 + 54 + $box = id(new PHUIObjectBoxView()) 55 + ->setHeader($header) 56 + ->appendChild($properties); 57 + 58 + $xactions = id(new FundInitiativeTransactionQuery()) 59 + ->setViewer($viewer) 60 + ->withObjectPHIDs(array($initiative->getPHID())) 61 + ->execute(); 62 + 63 + $timeline = id(new PhabricatorApplicationTransactionView()) 64 + ->setUser($viewer) 65 + ->setObjectPHID($initiative->getPHID()) 66 + ->setTransactions($xactions); 67 + 68 + return $this->buildApplicationPage( 69 + array( 70 + $crumbs, 71 + $box, 72 + $timeline, 73 + ), 74 + array( 75 + 'title' => $title, 76 + )); 77 + } 78 + 79 + private function buildPropertyListView(FundInitiative $initiative) { 80 + $viewer = $this->getRequest()->getUser(); 81 + 82 + $view = id(new PHUIPropertyListView()) 83 + ->setUser($viewer) 84 + ->setObject($initiative); 85 + 86 + $owner_phid = $initiative->getOwnerPHID(); 87 + $this->loadHandles(array($owner_phid)); 88 + 89 + $view->addProperty( 90 + pht('Owner'), 91 + $this->getHandle($owner_phid)->renderLink()); 92 + 93 + $view->invokeWillRenderEvent(); 94 + 95 + $description = $initiative->getDescription(); 96 + if (strlen($description)) { 97 + $description = PhabricatorMarkupEngine::renderOneObject( 98 + id(new PhabricatorMarkupOneOff())->setContent($description), 99 + 'default', 100 + $viewer); 101 + 102 + $view->addSectionHeader(pht('Description')); 103 + $view->addTextContent($description); 104 + } 105 + 106 + return $view; 107 + } 108 + 109 + private function buildActionListView(FundInitiative $initiative) { 110 + $viewer = $this->getRequest()->getUser(); 111 + $id = $initiative->getID(); 112 + 113 + $can_edit = PhabricatorPolicyFilter::hasCapability( 114 + $viewer, 115 + $initiative, 116 + PhabricatorPolicyCapability::CAN_EDIT); 117 + 118 + $view = id(new PhabricatorActionListView()) 119 + ->setUser($viewer) 120 + ->setObject($initiative); 121 + 122 + $view->addAction( 123 + id(new PhabricatorActionView()) 124 + ->setName(pht('Edit Initiative')) 125 + ->setIcon('fa-pencil') 126 + ->setDisabled(!$can_edit) 127 + ->setWorkflow(!$can_edit) 128 + ->setHref($this->getApplicationURI("/edit/{$id}/"))); 129 + 130 + if ($initiative->isClosed()) { 131 + $close_name = pht('Reopen Initiative'); 132 + $close_icon = 'fa-check'; 133 + } else { 134 + $close_name = pht('Close Initiative'); 135 + $close_icon = 'fa-times'; 136 + } 137 + 138 + $view->addAction( 139 + id(new PhabricatorActionView()) 140 + ->setName($close_name) 141 + ->setIcon($close_icon) 142 + ->setDisabled(!$can_edit) 143 + ->setWorkflow(true) 144 + ->setHref($this->getApplicationURI("/close/{$id}/"))); 145 + 146 + return $view; 147 + } 148 + 149 + }
+13
src/applications/fund/editor/FundBackingEditor.php
··· 1 + <?php 2 + 3 + final class FundBackingEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorFundApplication'; 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Fund Backing'); 12 + } 13 + }
+123
src/applications/fund/editor/FundInitiativeEditor.php
··· 1 + <?php 2 + 3 + final class FundInitiativeEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorFundApplication'; 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Fund Initiatives'); 12 + } 13 + 14 + public function getTransactionTypes() { 15 + $types = parent::getTransactionTypes(); 16 + 17 + $types[] = FundInitiativeTransaction::TYPE_NAME; 18 + $types[] = FundInitiativeTransaction::TYPE_DESCRIPTION; 19 + $types[] = FundInitiativeTransaction::TYPE_STATUS; 20 + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; 21 + $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; 22 + 23 + return $types; 24 + } 25 + 26 + protected function getCustomTransactionOldValue( 27 + PhabricatorLiskDAO $object, 28 + PhabricatorApplicationTransaction $xaction) { 29 + switch ($xaction->getTransactionType()) { 30 + case FundInitiativeTransaction::TYPE_NAME: 31 + return $object->getName(); 32 + case FundInitiativeTransaction::TYPE_DESCRIPTION: 33 + return $object->getDescription(); 34 + case FundInitiativeTransaction::TYPE_STATUS: 35 + return $object->getStatus(); 36 + } 37 + 38 + return parent::getCustomTransactionOldValue($object, $xaction); 39 + } 40 + 41 + protected function getCustomTransactionNewValue( 42 + PhabricatorLiskDAO $object, 43 + PhabricatorApplicationTransaction $xaction) { 44 + 45 + switch ($xaction->getTransactionType()) { 46 + case FundInitiativeTransaction::TYPE_NAME: 47 + case FundInitiativeTransaction::TYPE_DESCRIPTION: 48 + case FundInitiativeTransaction::TYPE_STATUS: 49 + return $xaction->getNewValue(); 50 + } 51 + 52 + return parent::getCustomTransactionNewValue($object, $xaction); 53 + } 54 + 55 + protected function applyCustomInternalTransaction( 56 + PhabricatorLiskDAO $object, 57 + PhabricatorApplicationTransaction $xaction) { 58 + 59 + switch ($xaction->getTransactionType()) { 60 + case FundInitiativeTransaction::TYPE_NAME: 61 + $object->setName($xaction->getNewValue()); 62 + return; 63 + case FundInitiativeTransaction::TYPE_DESCRIPTION: 64 + $object->setDescription($xaction->getNewValue()); 65 + return; 66 + case FundInitiativeTransaction::TYPE_STATUS: 67 + $object->setStatus($xaction->getNewValue()); 68 + return; 69 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 70 + case PhabricatorTransactions::TYPE_EDGE: 71 + return; 72 + } 73 + 74 + return parent::applyCustomInternalTransaction($object, $xaction); 75 + } 76 + 77 + protected function applyCustomExternalTransaction( 78 + PhabricatorLiskDAO $object, 79 + PhabricatorApplicationTransaction $xaction) { 80 + 81 + switch ($xaction->getTransactionType()) { 82 + case FundInitiativeTransaction::TYPE_NAME: 83 + case FundInitiativeTransaction::TYPE_DESCRIPTION: 84 + case FundInitiativeTransaction::TYPE_STATUS: 85 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 86 + case PhabricatorTransactions::TYPE_EDGE: 87 + return; 88 + } 89 + 90 + return parent::applyCustomExternalTransaction($object, $xaction); 91 + } 92 + 93 + protected function validateTransaction( 94 + PhabricatorLiskDAO $object, 95 + $type, 96 + array $xactions) { 97 + 98 + $errors = parent::validateTransaction($object, $type, $xactions); 99 + 100 + switch ($type) { 101 + case FundInitiativeTransaction::TYPE_NAME: 102 + $missing = $this->validateIsEmptyTextField( 103 + $object->getName(), 104 + $xactions); 105 + 106 + if ($missing) { 107 + $error = new PhabricatorApplicationTransactionValidationError( 108 + $type, 109 + pht('Required'), 110 + pht('Initiative name is required.'), 111 + nonempty(last($xactions), null)); 112 + 113 + $error->setIsMissingFieldError(true); 114 + $errors[] = $error; 115 + } 116 + break; 117 + } 118 + 119 + return $errors; 120 + } 121 + 122 + 123 + }
+41
src/applications/fund/phid/FundBackingPHIDType.php
··· 1 + <?php 2 + 3 + final class FundBackingPHIDType extends PhabricatorPHIDType { 4 + 5 + const TYPECONST = 'FBAK'; 6 + 7 + public function getTypeName() { 8 + return pht('Variable'); 9 + } 10 + 11 + public function newObject() { 12 + return new FundInitiative(); 13 + } 14 + 15 + protected function buildQueryForObjects( 16 + PhabricatorObjectQuery $query, 17 + array $phids) { 18 + 19 + return id(new FundInitiativeQuery()) 20 + ->withPHIDs($phids); 21 + } 22 + 23 + public function loadHandles( 24 + PhabricatorHandleQuery $query, 25 + array $handles, 26 + array $objects) { 27 + 28 + foreach ($handles as $phid => $handle) { 29 + $initiative = $objects[$phid]; 30 + 31 + $id = $initiative->getID(); 32 + $monogram = $initiative->getMonogram(); 33 + $name = $initiative->getName(); 34 + 35 + $handle->setName($name); 36 + $handle->setFullName("{$monogram} {$name}"); 37 + $handle->setURI("/fund/view/{$id}/"); 38 + } 39 + } 40 + 41 + }
+74
src/applications/fund/phid/FundInitiativePHIDType.php
··· 1 + <?php 2 + 3 + final class FundInitiativePHIDType extends PhabricatorPHIDType { 4 + 5 + const TYPECONST = 'FITV'; 6 + 7 + public function getTypeName() { 8 + return pht('Initiative'); 9 + } 10 + 11 + public function newObject() { 12 + return new FundInitiative(); 13 + } 14 + 15 + protected function buildQueryForObjects( 16 + PhabricatorObjectQuery $query, 17 + array $phids) { 18 + 19 + return id(new FundInitiativeQuery()) 20 + ->withPHIDs($phids); 21 + } 22 + 23 + public function loadHandles( 24 + PhabricatorHandleQuery $query, 25 + array $handles, 26 + array $objects) { 27 + 28 + foreach ($handles as $phid => $handle) { 29 + $initiative = $objects[$phid]; 30 + 31 + $id = $initiative->getID(); 32 + $monogram = $initiative->getMonogram(); 33 + $name = $initiative->getName(); 34 + 35 + if ($initiative->isClosed()) { 36 + $handle->setStatus(PhabricatorObjectHandleStatus::STATUS_CLOSED); 37 + } 38 + 39 + $handle->setName($name); 40 + $handle->setFullName("{$monogram} {$name}"); 41 + $handle->setURI("/I{$id}"); 42 + } 43 + } 44 + 45 + public function canLoadNamedObject($name) { 46 + return preg_match('/^I\d*[1-9]\d*$/i', $name); 47 + } 48 + 49 + public function loadNamedObjects( 50 + PhabricatorObjectQuery $query, 51 + array $names) { 52 + 53 + $id_map = array(); 54 + foreach ($names as $name) { 55 + $id = (int)substr($name, 1); 56 + $id_map[$id][] = $name; 57 + } 58 + 59 + $objects = id(new FundInitiativeQuery()) 60 + ->setViewer($query->getViewer()) 61 + ->withIDs(array_keys($id_map)) 62 + ->execute(); 63 + 64 + $results = array(); 65 + foreach ($objects as $id => $object) { 66 + foreach (idx($id_map, $id, array()) as $name) { 67 + $results[$name] = $object; 68 + } 69 + } 70 + 71 + return $results; 72 + } 73 + 74 + }
+60
src/applications/fund/query/FundBackingQuery.php
··· 1 + <?php 2 + 3 + final class FundBackingQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $phids; 8 + 9 + public function withIDs(array $ids) { 10 + $this->ids = $ids; 11 + return $this; 12 + } 13 + 14 + public function withPHIDs(array $phids) { 15 + $this->phids = $phids; 16 + return $this; 17 + } 18 + 19 + protected function loadPage() { 20 + $table = new FundBacking(); 21 + $conn_r = $table->establishConnection('r'); 22 + 23 + $rows = queryfx_all( 24 + $conn_r, 25 + 'SELECT * FROM %T %Q %Q %Q', 26 + $table->getTableName(), 27 + $this->buildWhereClause($conn_r), 28 + $this->buildOrderClause($conn_r), 29 + $this->buildLimitClause($conn_r)); 30 + 31 + return $table->loadAllFromArray($rows); 32 + } 33 + 34 + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { 35 + $where = array(); 36 + 37 + $where[] = $this->buildPagingClause($conn_r); 38 + 39 + if ($this->ids !== null) { 40 + $where[] = qsprintf( 41 + $conn_r, 42 + 'id IN (%Ld)', 43 + $this->ids); 44 + } 45 + 46 + if ($this->phids !== null) { 47 + $where[] = qsprintf( 48 + $conn_r, 49 + 'phid IN (%Ls)', 50 + $this->phids); 51 + } 52 + 53 + return $this->formatWhereClause($where); 54 + } 55 + 56 + public function getQueryApplicationClass() { 57 + return 'PhabricatorFundApplication'; 58 + } 59 + 60 + }
+10
src/applications/fund/query/FundBackingTransactionQuery.php
··· 1 + <?php 2 + 3 + final class FundBackingTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new FundBackingTransaction(); 8 + } 9 + 10 + }
+116
src/applications/fund/query/FundInitiativeQuery.php
··· 1 + <?php 2 + 3 + final class FundInitiativeQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $phids; 8 + private $ownerPHIDs; 9 + private $statuses; 10 + 11 + private $needProjectPHIDs; 12 + 13 + public function withIDs(array $ids) { 14 + $this->ids = $ids; 15 + return $this; 16 + } 17 + 18 + public function withPHIDs(array $phids) { 19 + $this->phids = $phids; 20 + return $this; 21 + } 22 + 23 + public function withOwnerPHIDs(array $phids) { 24 + $this->ownerPHIDs = $phids; 25 + return $this; 26 + } 27 + 28 + public function withStatuses(array $statuses) { 29 + $this->statuses = $statuses; 30 + return $this; 31 + } 32 + 33 + public function needProjectPHIDs($need) { 34 + $this->needProjectPHIDs = $need; 35 + return $this; 36 + } 37 + 38 + protected function loadPage() { 39 + $table = new FundInitiative(); 40 + $conn_r = $table->establishConnection('r'); 41 + 42 + $rows = queryfx_all( 43 + $conn_r, 44 + 'SELECT * FROM %T %Q %Q %Q', 45 + $table->getTableName(), 46 + $this->buildWhereClause($conn_r), 47 + $this->buildOrderClause($conn_r), 48 + $this->buildLimitClause($conn_r)); 49 + 50 + return $table->loadAllFromArray($rows); 51 + } 52 + 53 + protected function didFilterPage(array $initiatives) { 54 + 55 + if ($this->needProjectPHIDs) { 56 + $edge_query = id(new PhabricatorEdgeQuery()) 57 + ->withSourcePHIDs(mpull($initiatives, 'getPHID')) 58 + ->withEdgeTypes( 59 + array( 60 + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, 61 + )); 62 + $edge_query->execute(); 63 + 64 + foreach ($initiatives as $initiative) { 65 + $phids = $edge_query->getDestinationPHIDs( 66 + array( 67 + $initiative->getPHID(), 68 + )); 69 + $initiative->attachProjectPHIDs($phids); 70 + } 71 + } 72 + 73 + return $initiatives; 74 + } 75 + 76 + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { 77 + $where = array(); 78 + 79 + $where[] = $this->buildPagingClause($conn_r); 80 + 81 + if ($this->ids !== null) { 82 + $where[] = qsprintf( 83 + $conn_r, 84 + 'id IN (%Ld)', 85 + $this->ids); 86 + } 87 + 88 + if ($this->phids !== null) { 89 + $where[] = qsprintf( 90 + $conn_r, 91 + 'phid IN (%Ls)', 92 + $this->phids); 93 + } 94 + 95 + if ($this->ownerPHIDs !== null) { 96 + $where[] = qsprintf( 97 + $conn_r, 98 + 'ownerPHID IN (%Ls)', 99 + $this->ownerPHIDs); 100 + } 101 + 102 + if ($this->statuses !== null) { 103 + $where[] = qsprintf( 104 + $conn_r, 105 + 'status IN (%Ls)', 106 + $this->statuses); 107 + } 108 + 109 + return $this->formatWhereClause($where); 110 + } 111 + 112 + public function getQueryApplicationClass() { 113 + return 'PhabricatorFundApplication'; 114 + } 115 + 116 + }
+180
src/applications/fund/query/FundInitiativeSearchEngine.php
··· 1 + <?php 2 + 3 + final class FundInitiativeSearchEngine 4 + extends PhabricatorApplicationSearchEngine { 5 + 6 + public function getResultTypeDescription() { 7 + return pht('Fund Initiatives'); 8 + } 9 + 10 + public function getApplicationClassName() { 11 + return 'PhabricatorFundApplication'; 12 + } 13 + 14 + public function buildSavedQueryFromRequest(AphrontRequest $request) { 15 + $saved = new PhabricatorSavedQuery(); 16 + 17 + $saved->setParameter( 18 + 'ownerPHIDs', 19 + $this->readUsersFromRequest($request, 'owners')); 20 + 21 + $saved->setParameter( 22 + 'statuses', 23 + $this->readListFromRequest($request, 'statuses')); 24 + 25 + return $saved; 26 + } 27 + 28 + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { 29 + $query = id(new FundInitiativeQuery()) 30 + ->needProjectPHIDs(true); 31 + 32 + $owner_phids = $saved->getParameter('ownerPHIDs'); 33 + if ($owner_phids) { 34 + $query->withOwnerPHIDs($owner_phids); 35 + } 36 + 37 + $statuses = $saved->getParameter('statuses'); 38 + if ($statuses) { 39 + $query->withStatuses($statuses); 40 + } 41 + 42 + return $query; 43 + } 44 + 45 + public function buildSearchForm( 46 + AphrontFormView $form, 47 + PhabricatorSavedQuery $saved) { 48 + 49 + $statuses = $saved->getParameter('statuses', array()); 50 + $statuses = array_fuse($statuses); 51 + 52 + $owner_phids = $saved->getParameter('ownerPHIDs', array()); 53 + 54 + $all_phids = array_mergev( 55 + array( 56 + $owner_phids, 57 + )); 58 + 59 + $handles = id(new PhabricatorHandleQuery()) 60 + ->setViewer($this->requireViewer()) 61 + ->withPHIDs($all_phids) 62 + ->execute(); 63 + 64 + $status_map = FundInitiative::getStatusNameMap(); 65 + $status_control = id(new AphrontFormCheckboxControl()) 66 + ->setLabel(pht('Statuses')); 67 + foreach ($status_map as $status => $name) { 68 + $status_control->addCheckbox( 69 + 'statuses[]', 70 + $status, 71 + $name, 72 + isset($statuses[$status])); 73 + } 74 + 75 + $form 76 + ->appendChild( 77 + id(new AphrontFormTokenizerControl()) 78 + ->setLabel(pht('Owners')) 79 + ->setName('owners') 80 + ->setDatasource(new PhabricatorPeopleDatasource()) 81 + ->setValue(array_select_keys($handles, $owner_phids))) 82 + ->appendChild($status_control); 83 + } 84 + 85 + protected function getURI($path) { 86 + return '/fund/'.$path; 87 + } 88 + 89 + public function getBuiltinQueryNames() { 90 + $names = array(); 91 + 92 + $names['open'] = pht('Open Initiatives'); 93 + if ($this->requireViewer()->isLoggedIn()) { 94 + $names['owned'] = pht('Owned Initiatives'); 95 + } 96 + $names['all'] = pht('All Initiatives'); 97 + 98 + return $names; 99 + } 100 + 101 + public function buildSavedQueryFromBuiltin($query_key) { 102 + $query = $this->newSavedQuery(); 103 + $query->setQueryKey($query_key); 104 + 105 + switch ($query_key) { 106 + case 'all': 107 + return $query; 108 + case 'owned': 109 + return $query->setParameter( 110 + 'ownerPHIDs', 111 + array( 112 + $this->requireViewer()->getPHID(), 113 + )); 114 + case 'open': 115 + return $query->setParameter( 116 + 'statuses', 117 + array( 118 + FundInitiative::STATUS_OPEN, 119 + )); 120 + } 121 + 122 + return parent::buildSavedQueryFromBuiltin($query_key); 123 + } 124 + 125 + protected function getRequiredHandlePHIDsForResultList( 126 + array $initiatives, 127 + PhabricatorSavedQuery $query) { 128 + 129 + $phids = array(); 130 + foreach ($initiatives as $initiative) { 131 + $phids[] = $initiative->getOwnerPHID(); 132 + foreach ($initiative->getProjectPHIDs() as $project_phid) { 133 + $phids[] = $project_phid; 134 + } 135 + } 136 + 137 + return $phids; 138 + } 139 + 140 + protected function renderResultList( 141 + array $initiatives, 142 + PhabricatorSavedQuery $query, 143 + array $handles) { 144 + assert_instances_of($initiatives, 'FundInitiative'); 145 + 146 + $viewer = $this->requireViewer(); 147 + 148 + $list = id(new PHUIObjectItemListView()); 149 + foreach ($initiatives as $initiative) { 150 + $owner_handle = $handles[$initiative->getOwnerPHID()]; 151 + 152 + $item = id(new PHUIObjectItemView()) 153 + ->setObjectName($initiative->getMonogram()) 154 + ->setHeader($initiative->getName()) 155 + ->setHref('/'.$initiative->getMonogram()) 156 + ->addByline(pht('Owner: %s', $owner_handle->renderLink())); 157 + 158 + if ($initiative->isClosed()) { 159 + $item->setDisabled(true); 160 + } 161 + 162 + $project_handles = array_select_keys( 163 + $handles, 164 + $initiative->getProjectPHIDs()); 165 + if ($project_handles) { 166 + $item->addAttribute( 167 + id(new PHUIHandleTagListView()) 168 + ->setLimit(4) 169 + ->setSlim(true) 170 + ->setHandles($project_handles)); 171 + } 172 + 173 + $list->addItem($item); 174 + } 175 + 176 + 177 + return $list; 178 + } 179 + 180 + }
+10
src/applications/fund/query/FundInitiativeTransactionQuery.php
··· 1 + <?php 2 + 3 + final class FundInitiativeTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new FundInitiativeTransaction(); 8 + } 9 + 10 + }
+18
src/applications/fund/remarkup/FundInitiativeRemarkupRule.php
··· 1 + <?php 2 + 3 + final class FundInitiativeRemarkupRule extends PhabricatorObjectRemarkupRule { 4 + 5 + protected function getObjectNamePrefix() { 6 + return 'I'; 7 + } 8 + 9 + protected function loadObjects(array $ids) { 10 + $viewer = $this->getEngine()->getConfig('viewer'); 11 + 12 + return id(new FundInitiativeQuery()) 13 + ->setViewer($viewer) 14 + ->withIDs($ids) 15 + ->execute(); 16 + } 17 + 18 + }
+85
src/applications/fund/storage/FundBacking.php
··· 1 + <?php 2 + 3 + final class FundBacking extends FundDAO 4 + implements 5 + PhabricatorPolicyInterface, 6 + PhabricatorApplicationTransactionInterface { 7 + 8 + protected $initiativePHID; 9 + protected $backerPHID; 10 + protected $purchasePHID; 11 + protected $amountInCents; 12 + protected $status; 13 + protected $properties = array(); 14 + 15 + private $initiative = self::ATTACHABLE; 16 + 17 + public function getConfiguration() { 18 + return array( 19 + self::CONFIG_AUX_PHID => true, 20 + ) + parent::getConfiguration(); 21 + } 22 + 23 + public function generatePHID() { 24 + return PhabricatorPHID::generateNewPHID(FundBackingPHIDType::TYPECONST); 25 + } 26 + 27 + public function getProperty($key, $default = null) { 28 + return idx($this->properties, $key, $default); 29 + } 30 + 31 + public function setProperty($key, $value) { 32 + $this->properties[$key] = $value; 33 + return $this; 34 + } 35 + 36 + 37 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 38 + 39 + 40 + public function getCapabilities() { 41 + return array( 42 + PhabricatorPolicyCapability::CAN_VIEW, 43 + ); 44 + } 45 + 46 + public function getPolicy($capability) { 47 + switch ($capability) { 48 + case PhabricatorPolicyCapability::CAN_VIEW: 49 + // If we have the initiative, use the initiative's policy. 50 + // Otherwise, return NOONE. This allows the backer to continue seeing 51 + // a backing even if they're no longer allowed to see the initiative. 52 + 53 + $initiative = $this->getInitiative(); 54 + if ($initiative) { 55 + return $initiative->getPolicy($capability); 56 + } 57 + return PhabricatorPolicies::POLICY_NOONE; 58 + } 59 + } 60 + 61 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 62 + return ($viewer->getPHID() == $this->getBackerPHID()); 63 + } 64 + 65 + public function describeAutomaticCapability($capability) { 66 + return pht('A backer can always see what they have backed.'); 67 + } 68 + 69 + 70 + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 71 + 72 + 73 + public function getApplicationTransactionEditor() { 74 + return new FundBackingEditor(); 75 + } 76 + 77 + public function getApplicationTransactionObject() { 78 + return $this; 79 + } 80 + 81 + public function getApplicationTransactionTemplate() { 82 + return new FundBackingTransaction(); 83 + } 84 + 85 + }
+18
src/applications/fund/storage/FundBackingTransaction.php
··· 1 + <?php 2 + 3 + final class FundBackingTransaction 4 + extends PhabricatorApplicationTransaction { 5 + 6 + public function getApplicationName() { 7 + return 'fund'; 8 + } 9 + 10 + public function getApplicationTransactionType() { 11 + return FundBackingPHIDType::TYPECONST; 12 + } 13 + 14 + public function getApplicationTransactionCommentObject() { 15 + return null; 16 + } 17 + 18 + }
+9
src/applications/fund/storage/FundDAO.php
··· 1 + <?php 2 + 3 + abstract class FundDAO extends PhabricatorLiskDAO { 4 + 5 + public function getApplicationName() { 6 + return 'fund'; 7 + } 8 + 9 + }
+157
src/applications/fund/storage/FundInitiative.php
··· 1 + <?php 2 + 3 + final class FundInitiative extends FundDAO 4 + implements 5 + PhabricatorPolicyInterface, 6 + PhabricatorProjectInterface, 7 + PhabricatorApplicationTransactionInterface, 8 + PhabricatorSubscribableInterface, 9 + PhabricatorMentionableInterface, 10 + PhabricatorFlaggableInterface, 11 + PhabricatorTokenReceiverInterface, 12 + PhabricatorDestructibleInterface { 13 + 14 + protected $name; 15 + protected $ownerPHID; 16 + protected $description; 17 + protected $viewPolicy; 18 + protected $editPolicy; 19 + protected $status; 20 + 21 + private $projectPHIDs = self::ATTACHABLE; 22 + 23 + const STATUS_OPEN = 'open'; 24 + const STATUS_CLOSED = 'closed'; 25 + 26 + public static function getStatusNameMap() { 27 + return array( 28 + self::STATUS_OPEN => pht('Open'), 29 + self::STATUS_CLOSED => pht('Closed'), 30 + ); 31 + } 32 + 33 + public static function initializeNewInitiative(PhabricatorUser $actor) { 34 + $app = id(new PhabricatorApplicationQuery()) 35 + ->setViewer($actor) 36 + ->withClasses(array('PhabricatorFundApplication')) 37 + ->executeOne(); 38 + 39 + $view_policy = $app->getPolicy(FundDefaultViewCapability::CAPABILITY); 40 + 41 + return id(new FundInitiative()) 42 + ->setOwnerPHID($actor->getPHID()) 43 + ->setViewPolicy($view_policy) 44 + ->setEditPolicy($actor->getPHID()) 45 + ->setStatus(self::STATUS_OPEN); 46 + } 47 + 48 + public function getConfiguration() { 49 + return array( 50 + self::CONFIG_AUX_PHID => true, 51 + ) + parent::getConfiguration(); 52 + } 53 + 54 + public function generatePHID() { 55 + return PhabricatorPHID::generateNewPHID(FundInitiativePHIDType::TYPECONST); 56 + } 57 + 58 + public function getMonogram() { 59 + return 'I'.$this->getID(); 60 + } 61 + 62 + public function getProjectPHIDs() { 63 + return $this->assertAttached($this->projectPHIDs); 64 + } 65 + 66 + public function attachProjectPHIDs(array $phids) { 67 + $this->projectPHIDs = $phids; 68 + return $this; 69 + } 70 + 71 + public function isClosed() { 72 + return ($this->getStatus() == self::STATUS_CLOSED); 73 + } 74 + 75 + 76 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 77 + 78 + 79 + public function getCapabilities() { 80 + return array( 81 + PhabricatorPolicyCapability::CAN_VIEW, 82 + PhabricatorPolicyCapability::CAN_EDIT, 83 + ); 84 + } 85 + 86 + public function getPolicy($capability) { 87 + switch ($capability) { 88 + case PhabricatorPolicyCapability::CAN_VIEW: 89 + return $this->getViewPolicy(); 90 + case PhabricatorPolicyCapability::CAN_EDIT: 91 + return $this->getEditPolicy(); 92 + } 93 + } 94 + 95 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 96 + return false; 97 + } 98 + 99 + public function describeAutomaticCapability($capability) { 100 + return null; 101 + } 102 + 103 + 104 + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 105 + 106 + 107 + public function getApplicationTransactionEditor() { 108 + return new FundInitiativeEditor(); 109 + } 110 + 111 + public function getApplicationTransactionObject() { 112 + return $this; 113 + } 114 + 115 + public function getApplicationTransactionTemplate() { 116 + return new FundInitiativeTransaction(); 117 + } 118 + 119 + 120 + /* -( PhabricatorSubscribableInterface )----------------------------------- */ 121 + 122 + 123 + public function isAutomaticallySubscribed($phid) { 124 + return ($phid == $this->getOwnerPHID()); 125 + } 126 + 127 + public function shouldShowSubscribersProperty() { 128 + return true; 129 + } 130 + 131 + public function shouldAllowSubscription($phid) { 132 + return true; 133 + } 134 + 135 + 136 + /* -( PhabricatorTokenRecevierInterface )---------------------------------- */ 137 + 138 + 139 + public function getUsersToNotifyOfTokenGiven() { 140 + return array( 141 + $this->getOwnerPHID(), 142 + ); 143 + } 144 + 145 + 146 + /* -( PhabricatorDestructibleInterface )----------------------------------- */ 147 + 148 + 149 + public function destroyObjectPermanently( 150 + PhabricatorDestructionEngine $engine) { 151 + 152 + $this->openTransaction(); 153 + $this->delete(); 154 + $this->saveTransaction(); 155 + } 156 + 157 + }
+136
src/applications/fund/storage/FundInitiativeTransaction.php
··· 1 + <?php 2 + 3 + final class FundInitiativeTransaction 4 + extends PhabricatorApplicationTransaction { 5 + 6 + const TYPE_NAME = 'fund:name'; 7 + const TYPE_DESCRIPTION = 'fund:description'; 8 + const TYPE_STATUS = 'fund:status'; 9 + 10 + public function getApplicationName() { 11 + return 'fund'; 12 + } 13 + 14 + public function getApplicationTransactionType() { 15 + return FundInitiativePHIDType::TYPECONST; 16 + } 17 + 18 + public function getApplicationTransactionCommentObject() { 19 + return null; 20 + } 21 + 22 + public function getTitle() { 23 + $author_phid = $this->getAuthorPHID(); 24 + $object_phid = $this->getObjectPHID(); 25 + 26 + $old = $this->getOldValue(); 27 + $new = $this->getNewValue(); 28 + 29 + $type = $this->getTransactionType(); 30 + switch ($type) { 31 + case FundInitiativeTransaction::TYPE_NAME: 32 + if ($old === null) { 33 + return pht( 34 + '%s created this initiative.', 35 + $this->renderHandleLink($author_phid)); 36 + } else { 37 + return pht( 38 + '%s renamed this initiative from "%s" to "%s".', 39 + $this->renderHandleLink($author_phid), 40 + $old, 41 + $new); 42 + } 43 + break; 44 + case FundInitiativeTransaction::TYPE_DESCRIPTION: 45 + return pht( 46 + '%s edited the description of this initiative.', 47 + $this->renderHandleLink($author_phid)); 48 + case FundInitiativeTransaction::TYPE_STATUS: 49 + switch ($new) { 50 + case FundInitiative::STATUS_OPEN: 51 + return pht( 52 + '%s reopened this initiative.', 53 + $this->renderHandleLink($author_phid)); 54 + case FundInitiative::STATUS_CLOSED: 55 + return pht( 56 + '%s closed this initiative.', 57 + $this->renderHandleLink($author_phid)); 58 + } 59 + break; 60 + } 61 + 62 + return parent::getTitle(); 63 + } 64 + 65 + public function getTitleForFeed(PhabricatorFeedStory $story) { 66 + $author_phid = $this->getAuthorPHID(); 67 + $object_phid = $this->getObjectPHID(); 68 + 69 + $old = $this->getOldValue(); 70 + $new = $this->getNewValue(); 71 + 72 + $type = $this->getTransactionType(); 73 + switch ($type) { 74 + case FundInitiativeTransaction::TYPE_NAME: 75 + if ($old === null) { 76 + return pht( 77 + '%s created %s.', 78 + $this->renderHandleLink($author_phid), 79 + $this->renderHandleLink($object_phid)); 80 + 81 + } else { 82 + return pht( 83 + '%s renamed %s.', 84 + $this->renderHandleLink($author_phid), 85 + $this->renderHandleLink($object_phid)); 86 + } 87 + break; 88 + case FundInitiativeTransaction::TYPE_DESCRIPTION: 89 + return pht( 90 + '%s updated the description for %s.', 91 + $this->renderHandleLink($author_phid), 92 + $this->renderHandleLink($object_phid)); 93 + case FundInitiativeTransaction::TYPE_STATUS: 94 + switch ($new) { 95 + case FundInitiative::STATUS_OPEN: 96 + return pht( 97 + '%s reopened %s.', 98 + $this->renderHandleLink($author_phid), 99 + $this->renderHandleLink($object_phid)); 100 + case FundInitiative::STATUS_CLOSED: 101 + return pht( 102 + '%s closed %s.', 103 + $this->renderHandleLink($author_phid), 104 + $this->renderHandleLink($object_phid)); 105 + } 106 + break; 107 + } 108 + 109 + return parent::getTitleForFeed($story); 110 + } 111 + 112 + public function shouldHide() { 113 + $old = $this->getOldValue(); 114 + switch ($this->getTransactionType()) { 115 + case FundInitiativeTransaction::TYPE_DESCRIPTION: 116 + return ($old === null); 117 + } 118 + return parent::shouldHide(); 119 + } 120 + 121 + public function hasChangeDetails() { 122 + switch ($this->getTransactionType()) { 123 + case FundInitiativeTransaction::TYPE_DESCRIPTION: 124 + return ($this->getOldValue() !== null); 125 + } 126 + 127 + return parent::hasChangeDetails(); 128 + } 129 + 130 + public function renderChangeDetails(PhabricatorUser $viewer) { 131 + return $this->renderTextCorpusChangeDetails( 132 + $viewer, 133 + $this->getOldValue(), 134 + $this->getNewValue()); 135 + } 136 + }
+1
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 119 119 'db.phragment' => array(), 120 120 'db.dashboard' => array(), 121 121 'db.system' => array(), 122 + 'db.fund' => array(), 122 123 '0000.legacy.sql' => array( 123 124 'legacy' => 0, 124 125 ),