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

Allow users to back initiatives in Fund

Summary:
Ref T5835. This is still completely made up (no payment integration), but you can "back" an initiative, type a number in the box, and generate a database row. You can then seach for backers and things you've backed and such.

Notable changes:

- Renamed "FundBacking" to "FundBacker". The former name was sort of because you can back things multiple times, but stuff like `$backings` was just too weird.
- I think that's it?

Test Plan:
- Backed an initiative.
- Viewed that I became a backer.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T5835

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

+623 -101
+14
resources/sql/autopatches/20140911.fund.4.backer.sql
··· 1 + CREATE TABLE {$NAMESPACE}_fund.fund_backer ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + initiativePHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 5 + backerPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 6 + status VARCHAR(32) NOT NULL COLLATE utf8_bin, 7 + amountInCents INT UNSIGNED NOT NULL, 8 + properties LONGTEXT NOT NULL COLLATE utf8_bin, 9 + dateCreated INT UNSIGNED NOT NULL, 10 + dateModified INT UNSIGNED NOT NULL, 11 + UNIQUE KEY `key_phid` (phid), 12 + KEY `key_initiative` (initiativePHID), 13 + KEY `key_backer` (backerPHID) 14 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+19
resources/sql/autopatches/20140911.fund.5.backxaction.sql
··· 1 + CREATE TABLE {$NAMESPACE}_fund.fund_backertransaction ( 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;
+18 -12
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', 657 + 'FundBacker' => 'applications/fund/storage/FundBacker.php', 658 + 'FundBackerEditor' => 'applications/fund/editor/FundBackerEditor.php', 659 + 'FundBackerListController' => 'applications/fund/controller/FundBackerListController.php', 660 + 'FundBackerPHIDType' => 'applications/fund/phid/FundBackerPHIDType.php', 661 + 'FundBackerQuery' => 'applications/fund/query/FundBackerQuery.php', 662 + 'FundBackerSearchEngine' => 'applications/fund/query/FundBackerSearchEngine.php', 663 + 'FundBackerTransaction' => 'applications/fund/storage/FundBackerTransaction.php', 664 + 'FundBackerTransactionQuery' => 'applications/fund/query/FundBackerTransactionQuery.php', 663 665 'FundController' => 'applications/fund/controller/FundController.php', 664 666 'FundCreateInitiativesCapability' => 'applications/fund/capability/FundCreateInitiativesCapability.php', 665 667 'FundDAO' => 'applications/fund/storage/FundDAO.php', 666 668 'FundDefaultViewCapability' => 'applications/fund/capability/FundDefaultViewCapability.php', 667 669 'FundInitiative' => 'applications/fund/storage/FundInitiative.php', 670 + 'FundInitiativeBackController' => 'applications/fund/controller/FundInitiativeBackController.php', 668 671 'FundInitiativeCloseController' => 'applications/fund/controller/FundInitiativeCloseController.php', 669 672 'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php', 670 673 'FundInitiativeEditor' => 'applications/fund/editor/FundInitiativeEditor.php', ··· 3445 3448 'FlagDeleteConduitAPIMethod' => 'FlagConduitAPIMethod', 3446 3449 'FlagEditConduitAPIMethod' => 'FlagConduitAPIMethod', 3447 3450 'FlagQueryConduitAPIMethod' => 'FlagConduitAPIMethod', 3448 - 'FundBacking' => array( 3451 + 'FundBacker' => array( 3449 3452 'FundDAO', 3450 3453 'PhabricatorPolicyInterface', 3451 3454 'PhabricatorApplicationTransactionInterface', 3452 3455 ), 3453 - 'FundBackingEditor' => 'PhabricatorApplicationTransactionEditor', 3454 - 'FundBackingPHIDType' => 'PhabricatorPHIDType', 3455 - 'FundBackingQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3456 - 'FundBackingTransaction' => 'PhabricatorApplicationTransaction', 3457 - 'FundBackingTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3456 + 'FundBackerEditor' => 'PhabricatorApplicationTransactionEditor', 3457 + 'FundBackerListController' => 'FundController', 3458 + 'FundBackerPHIDType' => 'PhabricatorPHIDType', 3459 + 'FundBackerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3460 + 'FundBackerSearchEngine' => 'PhabricatorApplicationSearchEngine', 3461 + 'FundBackerTransaction' => 'PhabricatorApplicationTransaction', 3462 + 'FundBackerTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3458 3463 'FundController' => 'PhabricatorController', 3459 3464 'FundCreateInitiativesCapability' => 'PhabricatorPolicyCapability', 3460 3465 'FundDAO' => 'PhabricatorLiskDAO', ··· 3470 3475 'PhabricatorTokenReceiverInterface', 3471 3476 'PhabricatorDestructibleInterface', 3472 3477 ), 3478 + 'FundInitiativeBackController' => 'FundController', 3473 3479 'FundInitiativeCloseController' => 'FundController', 3474 3480 'FundInitiativeEditController' => 'FundController', 3475 3481 'FundInitiativeEditor' => 'PhabricatorApplicationTransactionEditor',
+5 -2
src/applications/fund/application/PhabricatorFundApplication.php
··· 42 42 '/fund/' => array( 43 43 '(?:query/(?P<queryKey>[^/]+)/)?' => 'FundInitiativeListController', 44 44 'create/' => 'FundInitiativeEditController', 45 - 'edit/(?:(?P<id>[^/]+)/)?' => 'FundInitiativeEditController', 46 - 'close/(?P<id>[^/]+)/' => 'FundInitiativeCloseController', 45 + 'edit/(?:(?P<id>\d+)/)?' => 'FundInitiativeEditController', 46 + 'close/(?P<id>\d+)/' => 'FundInitiativeCloseController', 47 + 'back/(?P<id>\d+)/' => 'FundInitiativeBackController', 48 + 'backers/(?:(?P<id>\d+)/)?(?:query/(?P<queryKey>[^/]+)/)?' 49 + => 'FundBackerListController', 47 50 ), 48 51 ); 49 52 }
+78
src/applications/fund/controller/FundBackerListController.php
··· 1 + <?php 2 + 3 + final class FundBackerListController 4 + extends FundController { 5 + 6 + private $id; 7 + private $queryKey; 8 + private $initiative; 9 + 10 + public function willProcessRequest(array $data) { 11 + $this->id = idx($data, 'id'); 12 + $this->queryKey = idx($data, 'queryKey'); 13 + } 14 + 15 + public function processRequest() { 16 + $request = $this->getRequest(); 17 + 18 + if ($this->id) { 19 + $this->initiative = id(new FundInitiativeQuery()) 20 + ->setViewer($request->getUser()) 21 + ->withIDs(array($this->id)) 22 + ->executeOne(); 23 + if (!$this->initiative) { 24 + return new Aphront404Response(); 25 + } 26 + } 27 + 28 + $controller = id(new PhabricatorApplicationSearchController($request)) 29 + ->setQueryKey($this->queryKey) 30 + ->setSearchEngine($this->getEngine()) 31 + ->setNavigation($this->buildSideNavView()); 32 + 33 + return $this->delegateToController($controller); 34 + } 35 + 36 + public function buildSideNavView() { 37 + $user = $this->getRequest()->getUser(); 38 + 39 + $nav = new AphrontSideNavFilterView(); 40 + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 41 + 42 + $this->getEngine()->addNavigationItems($nav->getMenu()); 43 + 44 + $nav->selectFilter(null); 45 + 46 + return $nav; 47 + } 48 + 49 + protected function buildApplicationCrumbs() { 50 + $crumbs = parent::buildApplicationCrumbs(); 51 + $crumbs->addTextCrumb( 52 + pht('Backers'), 53 + $this->getApplicationURI('backers/')); 54 + 55 + if ($this->initiative) { 56 + $crumbs->addTextCrumb( 57 + $this->initiative->getMonogram(), 58 + '/'.$this->initiative->getMonogram()); 59 + } 60 + 61 + return $crumbs; 62 + } 63 + 64 + private function getEngine() { 65 + $request = $this->getRequest(); 66 + $viewer = $request->getUser(); 67 + 68 + $engine = id(new FundBackerSearchEngine()) 69 + ->setViewer($viewer); 70 + 71 + if ($this->initiative) { 72 + $engine->setInitiative($this->initiative); 73 + } 74 + 75 + return $engine; 76 + } 77 + 78 + }
+100
src/applications/fund/controller/FundInitiativeBackController.php
··· 1 + <?php 2 + 3 + final class FundInitiativeBackController 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 + $initiative_uri = '/'.$initiative->getMonogram(); 25 + 26 + if ($initiative->isClosed()) { 27 + return $this->newDialog() 28 + ->setTitle(pht('Initiative Closed')) 29 + ->appendParagraph( 30 + pht('You can not back a closed initiative.')) 31 + ->addCancelButton($initiative_uri); 32 + } 33 + 34 + $v_amount = null; 35 + $e_amount = true; 36 + $errors = array(); 37 + if ($request->isFormPost()) { 38 + $v_amount = $request->getStr('amount'); 39 + 40 + if (!strlen($v_amount)) { 41 + $errors[] = pht( 42 + 'You must specify how much money you want to contribute to the '. 43 + 'initiative.'); 44 + $e_amount = pht('Required'); 45 + } else { 46 + try { 47 + $currency = PhortuneCurrency::newFromUserInput( 48 + $viewer, 49 + $v_amount); 50 + } catch (Exception $ex) { 51 + $errors[] = $ex->getMessage(); 52 + $e_amount = pht('Invalid'); 53 + } 54 + } 55 + 56 + if (!$errors) { 57 + $backer = FundBacker::initializeNewBacker($viewer) 58 + ->setInitiativePHID($initiative->getPHID()) 59 + ->attachInitiative($initiative) 60 + ->setAmountInCents($currency->getValue()) 61 + ->save(); 62 + 63 + // TODO: Here, we'd create a purchase and cart. 64 + 65 + $xactions = array(); 66 + 67 + $xactions[] = id(new FundBackerTransaction()) 68 + ->setTransactionType(FundBackerTransaction::TYPE_STATUS) 69 + ->setNewValue(FundBacker::STATUS_IN_CART); 70 + 71 + $editor = id(new FundBackerEditor()) 72 + ->setActor($viewer) 73 + ->setContentSourceFromRequest($request); 74 + 75 + $editor->applyTransactions($backer, $xactions); 76 + 77 + // TODO: Here, we'd ship the user into Phortune. 78 + 79 + return id(new AphrontRedirectResponse())->setURI($initiative_uri); 80 + } 81 + } 82 + 83 + $form = id(new AphrontFormView()) 84 + ->setUser($viewer) 85 + ->appendChild( 86 + id(new AphrontFormTextControl()) 87 + ->setName('amount') 88 + ->setLabel(pht('Amount')) 89 + ->setValue($v_amount) 90 + ->setError($e_amount)); 91 + 92 + return $this->newDialog() 93 + ->setTitle(pht('Back Initiative')) 94 + ->setErrors($errors) 95 + ->appendChild($form->buildLayoutView()) 96 + ->addCancelButton($initiative_uri) 97 + ->addSubmitButton(pht('Continue')); 98 + } 99 + 100 + }
+3 -1
src/applications/fund/controller/FundInitiativeCloseController.php
··· 55 55 56 56 if ($is_close) { 57 57 $title = pht('Close Initiative?'); 58 - $body = pht('Really close this initiative?'); 58 + $body = pht( 59 + 'Really close this initiative? Users will no longer be able to '. 60 + 'back it.'); 59 61 $button_text = pht('Close Initiative'); 60 62 } else { 61 63 $title = pht('Reopen Initiative?');
+4
src/applications/fund/controller/FundInitiativeListController.php
··· 28 28 id(new FundInitiativeSearchEngine()) 29 29 ->setViewer($user) 30 30 ->addNavigationItems($nav->getMenu()); 31 + 32 + $nav->addLabel(pht('Backers')); 33 + $nav->addFilter('backers/', pht('Find Backers')); 34 + 31 35 $nav->selectFilter(null); 32 36 33 37 return $nav;
+14
src/applications/fund/controller/FundInitiativeViewController.php
··· 143 143 ->setWorkflow(true) 144 144 ->setHref($this->getApplicationURI("/close/{$id}/"))); 145 145 146 + $view->addAction( 147 + id(new PhabricatorActionView()) 148 + ->setName(pht('Back Initiative')) 149 + ->setIcon('fa-money') 150 + ->setDisabled($initiative->isClosed()) 151 + ->setWorkflow(true) 152 + ->setHref($this->getApplicationURI("/back/{$id}/"))); 153 + 154 + $view->addAction( 155 + id(new PhabricatorActionView()) 156 + ->setName(pht('View Backers')) 157 + ->setIcon('fa-bank') 158 + ->setHref($this->getApplicationURI("/backers/{$id}/"))); 159 + 146 160 return $view; 147 161 } 148 162
+69
src/applications/fund/editor/FundBackerEditor.php
··· 1 + <?php 2 + 3 + final class FundBackerEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorFundApplication'; 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Fund Backing'); 12 + } 13 + 14 + public function getTransactionTypes() { 15 + $types = parent::getTransactionTypes(); 16 + $types[] = FundBackerTransaction::TYPE_STATUS; 17 + 18 + return $types; 19 + } 20 + 21 + protected function getCustomTransactionOldValue( 22 + PhabricatorLiskDAO $object, 23 + PhabricatorApplicationTransaction $xaction) { 24 + switch ($xaction->getTransactionType()) { 25 + case FundBackerTransaction::TYPE_STATUS: 26 + return $object->getStatus(); 27 + } 28 + 29 + return parent::getCustomTransactionOldValue($object, $xaction); 30 + } 31 + 32 + protected function getCustomTransactionNewValue( 33 + PhabricatorLiskDAO $object, 34 + PhabricatorApplicationTransaction $xaction) { 35 + 36 + switch ($xaction->getTransactionType()) { 37 + case FundBackerTransaction::TYPE_STATUS: 38 + return $xaction->getNewValue(); 39 + } 40 + 41 + return parent::getCustomTransactionNewValue($object, $xaction); 42 + } 43 + 44 + protected function applyCustomInternalTransaction( 45 + PhabricatorLiskDAO $object, 46 + PhabricatorApplicationTransaction $xaction) { 47 + 48 + switch ($xaction->getTransactionType()) { 49 + case FundBackerTransaction::TYPE_STATUS: 50 + $object->setStatus($xaction->getNewValue()); 51 + return; 52 + } 53 + 54 + return parent::applyCustomInternalTransaction($object, $xaction); 55 + } 56 + 57 + protected function applyCustomExternalTransaction( 58 + PhabricatorLiskDAO $object, 59 + PhabricatorApplicationTransaction $xaction) { 60 + 61 + switch ($xaction->getTransactionType()) { 62 + case FundBackerTransaction::TYPE_STATUS: 63 + return; 64 + } 65 + 66 + return parent::applyCustomExternalTransaction($object, $xaction); 67 + } 68 + 69 + }
-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 - }
+1 -1
src/applications/fund/phid/FundBackingPHIDType.php src/applications/fund/phid/FundBackerPHIDType.php
··· 1 1 <?php 2 2 3 - final class FundBackingPHIDType extends PhabricatorPHIDType { 3 + final class FundBackerPHIDType extends PhabricatorPHIDType { 4 4 5 5 const TYPECONST = 'FBAK'; 6 6
+105
src/applications/fund/query/FundBackerQuery.php
··· 1 + <?php 2 + 3 + final class FundBackerQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $phids; 8 + 9 + private $initiativePHIDs; 10 + private $backerPHIDs; 11 + 12 + public function withIDs(array $ids) { 13 + $this->ids = $ids; 14 + return $this; 15 + } 16 + 17 + public function withPHIDs(array $phids) { 18 + $this->phids = $phids; 19 + return $this; 20 + } 21 + 22 + public function withInitiativePHIDs(array $phids) { 23 + $this->initiativePHIDs = $phids; 24 + return $this; 25 + } 26 + 27 + public function withBackerPHIDs(array $phids) { 28 + $this->backerPHIDs = $phids; 29 + return $this; 30 + } 31 + 32 + protected function loadPage() { 33 + $table = new FundBacker(); 34 + $conn_r = $table->establishConnection('r'); 35 + 36 + $rows = queryfx_all( 37 + $conn_r, 38 + 'SELECT * FROM %T %Q %Q %Q', 39 + $table->getTableName(), 40 + $this->buildWhereClause($conn_r), 41 + $this->buildOrderClause($conn_r), 42 + $this->buildLimitClause($conn_r)); 43 + 44 + return $table->loadAllFromArray($rows); 45 + } 46 + 47 + protected function willFilterPage(array $backers) { 48 + $initiative_phids = mpull($backers, 'getInitiativePHID'); 49 + $initiatives = id(new PhabricatorObjectQuery()) 50 + ->setParentQuery($this) 51 + ->setViewer($this->getViewer()) 52 + ->withPHIDs($initiative_phids) 53 + ->execute(); 54 + $initiatives = mpull($initiatives, null, 'getPHID'); 55 + 56 + foreach ($backers as $backer) { 57 + $initiative_phid = $backer->getInitiativePHID(); 58 + $initiative = idx($initiatives, $initiative_phid); 59 + $backer->attachInitiative($initiative); 60 + } 61 + 62 + return $backers; 63 + } 64 + 65 + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { 66 + $where = array(); 67 + 68 + $where[] = $this->buildPagingClause($conn_r); 69 + 70 + if ($this->ids !== null) { 71 + $where[] = qsprintf( 72 + $conn_r, 73 + 'id IN (%Ld)', 74 + $this->ids); 75 + } 76 + 77 + if ($this->phids !== null) { 78 + $where[] = qsprintf( 79 + $conn_r, 80 + 'phid IN (%Ls)', 81 + $this->phids); 82 + } 83 + 84 + if ($this->initiativePHIDs !== null) { 85 + $where[] = qsprintf( 86 + $conn_r, 87 + 'initiativePHID IN (%Ls)', 88 + $this->initiativePHIDs); 89 + } 90 + 91 + if ($this->backerPHIDs !== null) { 92 + $where[] = qsprintf( 93 + $conn_r, 94 + 'backerPHID IN (%Ls)', 95 + $this->backerPHIDs); 96 + } 97 + 98 + return $this->formatWhereClause($where); 99 + } 100 + 101 + public function getQueryApplicationClass() { 102 + return 'PhabricatorFundApplication'; 103 + } 104 + 105 + }
+153
src/applications/fund/query/FundBackerSearchEngine.php
··· 1 + <?php 2 + 3 + final class FundBackerSearchEngine 4 + extends PhabricatorApplicationSearchEngine { 5 + 6 + private $initiative; 7 + 8 + public function setInitiative(FundInitiative $initiative) { 9 + $this->initiative = $initiative; 10 + return $this; 11 + } 12 + 13 + public function getInitiative() { 14 + return $this->initiative; 15 + } 16 + 17 + public function getResultTypeDescription() { 18 + return pht('Fund Backers'); 19 + } 20 + 21 + public function getApplicationClassName() { 22 + return 'PhabricatorFundApplication'; 23 + } 24 + 25 + public function buildSavedQueryFromRequest(AphrontRequest $request) { 26 + $saved = new PhabricatorSavedQuery(); 27 + 28 + $saved->setParameter( 29 + 'backerPHIDs', 30 + $this->readUsersFromRequest($request, 'backers')); 31 + 32 + return $saved; 33 + } 34 + 35 + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { 36 + $query = id(new FundBackerQuery()); 37 + 38 + if ($this->getInitiative()) { 39 + $query->withInitiativePHIDs( 40 + array( 41 + $this->getInitiative()->getPHID(), 42 + )); 43 + } 44 + 45 + $backer_phids = $saved->getParameter('backerPHIDs'); 46 + if ($backer_phids) { 47 + $query->withBackerPHIDs($backer_phids); 48 + } 49 + 50 + return $query; 51 + } 52 + 53 + public function buildSearchForm( 54 + AphrontFormView $form, 55 + PhabricatorSavedQuery $saved) { 56 + 57 + 58 + $backer_phids = $saved->getParameter('backerPHIDs', array()); 59 + 60 + $all_phids = array_mergev( 61 + array( 62 + $backer_phids, 63 + )); 64 + 65 + $handles = id(new PhabricatorHandleQuery()) 66 + ->setViewer($this->requireViewer()) 67 + ->withPHIDs($all_phids) 68 + ->execute(); 69 + 70 + $form 71 + ->appendChild( 72 + id(new AphrontFormTokenizerControl()) 73 + ->setLabel(pht('Backers')) 74 + ->setName('backers') 75 + ->setDatasource(new PhabricatorPeopleDatasource()) 76 + ->setValue(array_select_keys($handles, $backer_phids))); 77 + } 78 + 79 + protected function getURI($path) { 80 + if ($this->getInitiative()) { 81 + return '/fund/backers/'.$this->getInitiative()->getID().'/'.$path; 82 + } else { 83 + return '/fund/backers/'.$path; 84 + } 85 + } 86 + 87 + public function getBuiltinQueryNames() { 88 + $names = array(); 89 + $names['all'] = pht('All Backers'); 90 + 91 + return $names; 92 + } 93 + 94 + public function buildSavedQueryFromBuiltin($query_key) { 95 + $query = $this->newSavedQuery(); 96 + $query->setQueryKey($query_key); 97 + 98 + switch ($query_key) { 99 + case 'all': 100 + return $query; 101 + } 102 + 103 + return parent::buildSavedQueryFromBuiltin($query_key); 104 + } 105 + 106 + protected function getRequiredHandlePHIDsForResultList( 107 + array $backers, 108 + PhabricatorSavedQuery $query) { 109 + 110 + $phids = array(); 111 + foreach ($backers as $backer) { 112 + $phids[] = $backer->getBackerPHID(); 113 + $phids[] = $backer->getInitiativePHID(); 114 + } 115 + 116 + return $phids; 117 + } 118 + 119 + protected function renderResultList( 120 + array $backers, 121 + PhabricatorSavedQuery $query, 122 + array $handles) { 123 + assert_instances_of($backers, 'FundBacker'); 124 + 125 + $viewer = $this->requireViewer(); 126 + 127 + $list = id(new PHUIObjectItemListView()); 128 + foreach ($backers as $backer) { 129 + $backer_handle = $handles[$backer->getBackerPHID()]; 130 + 131 + $currency = PhortuneCurrency::newFromUSDCents( 132 + $backer->getAmountInCents()); 133 + 134 + $header = pht( 135 + '%s for %s', 136 + $currency->formatForDisplay(), 137 + $handles[$backer->getInitiativePHID()]->renderLink()); 138 + 139 + $item = id(new PHUIObjectItemView()) 140 + ->setHeader($header) 141 + ->addIcon( 142 + 'none', 143 + phabricator_datetime($backer->getDateCreated(), $viewer)) 144 + ->addByline(pht('Backer: %s', $backer_handle->renderLink())); 145 + 146 + $list->addItem($item); 147 + } 148 + 149 + 150 + return $list; 151 + } 152 + 153 + }
-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 - }
+2 -2
src/applications/fund/query/FundBackingTransactionQuery.php src/applications/fund/query/FundBackerTransactionQuery.php
··· 1 1 <?php 2 2 3 - final class FundBackingTransactionQuery 3 + final class FundBackerTransactionQuery 4 4 extends PhabricatorApplicationTransactionQuery { 5 5 6 6 public function getTemplateApplicationTransaction() { 7 - return new FundBackingTransaction(); 7 + return new FundBackerTransaction(); 8 8 } 9 9 10 10 }
+31 -6
src/applications/fund/storage/FundBacking.php src/applications/fund/storage/FundBacker.php
··· 1 1 <?php 2 2 3 - final class FundBacking extends FundDAO 3 + final class FundBacker extends FundDAO 4 4 implements 5 5 PhabricatorPolicyInterface, 6 6 PhabricatorApplicationTransactionInterface { 7 7 8 8 protected $initiativePHID; 9 9 protected $backerPHID; 10 - protected $purchasePHID; 11 10 protected $amountInCents; 12 11 protected $status; 13 12 protected $properties = array(); 14 13 15 14 private $initiative = self::ATTACHABLE; 16 15 16 + const STATUS_NEW = 'new'; 17 + const STATUS_IN_CART = 'in-cart'; 18 + 19 + public static function initializeNewBacker(PhabricatorUser $actor) { 20 + return id(new FundBacker()) 21 + ->setBackerPHID($actor->getPHID()) 22 + ->setStatus(self::STATUS_NEW); 23 + } 24 + 17 25 public function getConfiguration() { 18 26 return array( 19 27 self::CONFIG_AUX_PHID => true, 28 + self::CONFIG_SERIALIZATION => array( 29 + 'properties' => self::SERIALIZATION_JSON, 30 + ), 20 31 ) + parent::getConfiguration(); 21 32 } 22 33 23 34 public function generatePHID() { 24 - return PhabricatorPHID::generateNewPHID(FundBackingPHIDType::TYPECONST); 35 + return PhabricatorPHID::generateNewPHID(FundBackerPHIDType::TYPECONST); 36 + } 37 + 38 + protected function didReadData() { 39 + // The payment processing code is strict about types. 40 + $this->amountInCents = (int)$this->amountInCents; 25 41 } 26 42 27 43 public function getProperty($key, $default = null) { ··· 33 49 return $this; 34 50 } 35 51 52 + public function getInitiative() { 53 + return $this->assertAttached($this->initiative); 54 + } 55 + 56 + public function attachInitiative(FundInitiative $initiative = null) { 57 + $this->initiative = $initiative; 58 + return $this; 59 + } 60 + 36 61 37 62 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 38 63 ··· 48 73 case PhabricatorPolicyCapability::CAN_VIEW: 49 74 // If we have the initiative, use the initiative's policy. 50 75 // Otherwise, return NOONE. This allows the backer to continue seeing 51 - // a backing even if they're no longer allowed to see the initiative. 76 + // a backer even if they're no longer allowed to see the initiative. 52 77 53 78 $initiative = $this->getInitiative(); 54 79 if ($initiative) { ··· 71 96 72 97 73 98 public function getApplicationTransactionEditor() { 74 - return new FundBackingEditor(); 99 + return new FundBackerEditor(); 75 100 } 76 101 77 102 public function getApplicationTransactionObject() { ··· 79 104 } 80 105 81 106 public function getApplicationTransactionTemplate() { 82 - return new FundBackingTransaction(); 107 + return new FundBackerTransaction(); 83 108 } 84 109 85 110 }
+4 -2
src/applications/fund/storage/FundBackingTransaction.php src/applications/fund/storage/FundBackerTransaction.php
··· 1 1 <?php 2 2 3 - final class FundBackingTransaction 3 + final class FundBackerTransaction 4 4 extends PhabricatorApplicationTransaction { 5 + 6 + const TYPE_STATUS = 'fund:backer:status'; 5 7 6 8 public function getApplicationName() { 7 9 return 'fund'; 8 10 } 9 11 10 12 public function getApplicationTransactionType() { 11 - return FundBackingPHIDType::TYPECONST; 13 + return FundBackerPHIDType::TYPECONST; 12 14 } 13 15 14 16 public function getApplicationTransactionCommentObject() {
+3 -2
src/applications/fund/storage/FundInitiative.php
··· 93 93 } 94 94 95 95 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 96 - return false; 96 + return ($viewer->getPHID() == $this->getOwnerPHID()); 97 97 } 98 98 99 99 public function describeAutomaticCapability($capability) { 100 - return null; 100 + return pht( 101 + 'The owner of an initiative can always view and edit it.'); 101 102 } 102 103 103 104