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
			
			
This commit is contained in:
		
							
								
								
									
										15
									
								
								resources/sql/autopatches/20140911.fund.1.initiative.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								resources/sql/autopatches/20140911.fund.1.initiative.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | CREATE TABLE {$NAMESPACE}_fund.fund_initiative ( | ||||||
|  |   id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, | ||||||
|  |   phid VARCHAR(64) NOT NULL COLLATE utf8_bin, | ||||||
|  |   name VARCHAR(255) NOT NULL, | ||||||
|  |   ownerPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, | ||||||
|  |   description LONGTEXT NOT NULL, | ||||||
|  |   viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, | ||||||
|  |   editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, | ||||||
|  |   status VARCHAR(32) NOT NULL COLLATE utf8_bin, | ||||||
|  |   dateCreated INT UNSIGNED NOT NULL, | ||||||
|  |   dateModified INT UNSIGNED NOT NULL, | ||||||
|  |   UNIQUE KEY `key_phid` (phid), | ||||||
|  |   KEY `key_status` (status), | ||||||
|  |   KEY `key_owner` (ownerPHID) | ||||||
|  | ) ENGINE=InnoDB, COLLATE utf8_general_ci; | ||||||
							
								
								
									
										19
									
								
								resources/sql/autopatches/20140911.fund.2.xaction.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								resources/sql/autopatches/20140911.fund.2.xaction.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | CREATE TABLE {$NAMESPACE}_fund.fund_initiativetransaction ( | ||||||
|  |   id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, | ||||||
|  |   phid VARCHAR(64) COLLATE utf8_bin NOT NULL, | ||||||
|  |   authorPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, | ||||||
|  |   objectPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, | ||||||
|  |   viewPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, | ||||||
|  |   editPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, | ||||||
|  |   commentPHID VARCHAR(64) COLLATE utf8_bin DEFAULT NULL, | ||||||
|  |   commentVersion INT UNSIGNED NOT NULL, | ||||||
|  |   transactionType VARCHAR(32) COLLATE utf8_bin NOT NULL, | ||||||
|  |   oldValue LONGTEXT COLLATE utf8_bin NOT NULL, | ||||||
|  |   newValue LONGTEXT COLLATE utf8_bin NOT NULL, | ||||||
|  |   contentSource LONGTEXT COLLATE utf8_bin NOT NULL, | ||||||
|  |   metadata LONGTEXT COLLATE utf8_bin NOT NULL, | ||||||
|  |   dateCreated INT UNSIGNED NOT NULL, | ||||||
|  |   dateModified INT UNSIGNED NOT NULL, | ||||||
|  |   UNIQUE KEY `key_phid` (`phid`), | ||||||
|  |   KEY `key_object` (`objectPHID`) | ||||||
|  | ) ENGINE=InnoDB, COLLATE utf8_general_ci; | ||||||
							
								
								
									
										15
									
								
								resources/sql/autopatches/20140911.fund.3.edge.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								resources/sql/autopatches/20140911.fund.3.edge.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | CREATE TABLE {$NAMESPACE}_fund.edge ( | ||||||
|  |   src VARCHAR(64) NOT NULL COLLATE utf8_bin, | ||||||
|  |   type VARCHAR(64) NOT NULL COLLATE utf8_bin, | ||||||
|  |   dst VARCHAR(64) NOT NULL COLLATE utf8_bin, | ||||||
|  |   dateCreated INT UNSIGNED NOT NULL, | ||||||
|  |   seq INT UNSIGNED NOT NULL, | ||||||
|  |   dataID INT UNSIGNED, | ||||||
|  |   PRIMARY KEY (src, type, dst), | ||||||
|  |   KEY (src, type, dateCreated, seq) | ||||||
|  | ) ENGINE=InnoDB, COLLATE utf8_general_ci; | ||||||
|  |  | ||||||
|  | CREATE TABLE {$NAMESPACE}_fund.edgedata ( | ||||||
|  |   id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, | ||||||
|  |   data LONGTEXT NOT NULL COLLATE utf8_bin | ||||||
|  | ) ENGINE=InnoDB, COLLATE utf8_general_ci; | ||||||
| @@ -654,6 +654,28 @@ phutil_register_library_map(array( | |||||||
|     'FlagDeleteConduitAPIMethod' => 'applications/flag/conduit/FlagDeleteConduitAPIMethod.php', |     'FlagDeleteConduitAPIMethod' => 'applications/flag/conduit/FlagDeleteConduitAPIMethod.php', | ||||||
|     'FlagEditConduitAPIMethod' => 'applications/flag/conduit/FlagEditConduitAPIMethod.php', |     'FlagEditConduitAPIMethod' => 'applications/flag/conduit/FlagEditConduitAPIMethod.php', | ||||||
|     'FlagQueryConduitAPIMethod' => 'applications/flag/conduit/FlagQueryConduitAPIMethod.php', |     'FlagQueryConduitAPIMethod' => 'applications/flag/conduit/FlagQueryConduitAPIMethod.php', | ||||||
|  |     'FundBacking' => 'applications/fund/storage/FundBacking.php', | ||||||
|  |     'FundBackingEditor' => 'applications/fund/editor/FundBackingEditor.php', | ||||||
|  |     'FundBackingPHIDType' => 'applications/fund/phid/FundBackingPHIDType.php', | ||||||
|  |     'FundBackingQuery' => 'applications/fund/query/FundBackingQuery.php', | ||||||
|  |     'FundBackingTransaction' => 'applications/fund/storage/FundBackingTransaction.php', | ||||||
|  |     'FundBackingTransactionQuery' => 'applications/fund/query/FundBackingTransactionQuery.php', | ||||||
|  |     'FundController' => 'applications/fund/controller/FundController.php', | ||||||
|  |     'FundCreateInitiativesCapability' => 'applications/fund/capability/FundCreateInitiativesCapability.php', | ||||||
|  |     'FundDAO' => 'applications/fund/storage/FundDAO.php', | ||||||
|  |     'FundDefaultViewCapability' => 'applications/fund/capability/FundDefaultViewCapability.php', | ||||||
|  |     'FundInitiative' => 'applications/fund/storage/FundInitiative.php', | ||||||
|  |     'FundInitiativeCloseController' => 'applications/fund/controller/FundInitiativeCloseController.php', | ||||||
|  |     'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php', | ||||||
|  |     'FundInitiativeEditor' => 'applications/fund/editor/FundInitiativeEditor.php', | ||||||
|  |     'FundInitiativeListController' => 'applications/fund/controller/FundInitiativeListController.php', | ||||||
|  |     'FundInitiativePHIDType' => 'applications/fund/phid/FundInitiativePHIDType.php', | ||||||
|  |     'FundInitiativeQuery' => 'applications/fund/query/FundInitiativeQuery.php', | ||||||
|  |     'FundInitiativeRemarkupRule' => 'applications/fund/remarkup/FundInitiativeRemarkupRule.php', | ||||||
|  |     'FundInitiativeSearchEngine' => 'applications/fund/query/FundInitiativeSearchEngine.php', | ||||||
|  |     'FundInitiativeTransaction' => 'applications/fund/storage/FundInitiativeTransaction.php', | ||||||
|  |     'FundInitiativeTransactionQuery' => 'applications/fund/query/FundInitiativeTransactionQuery.php', | ||||||
|  |     'FundInitiativeViewController' => 'applications/fund/controller/FundInitiativeViewController.php', | ||||||
|     'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php', |     'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php', | ||||||
|     'HarbormasterBuildAbortedException' => 'applications/harbormaster/exception/HarbormasterBuildAbortedException.php', |     'HarbormasterBuildAbortedException' => 'applications/harbormaster/exception/HarbormasterBuildAbortedException.php', | ||||||
|     'HarbormasterBuildActionController' => 'applications/harbormaster/controller/HarbormasterBuildActionController.php', |     'HarbormasterBuildActionController' => 'applications/harbormaster/controller/HarbormasterBuildActionController.php', | ||||||
| @@ -1600,6 +1622,7 @@ phutil_register_library_map(array( | |||||||
|     'PhabricatorFlagsApplication' => 'applications/flag/application/PhabricatorFlagsApplication.php', |     'PhabricatorFlagsApplication' => 'applications/flag/application/PhabricatorFlagsApplication.php', | ||||||
|     'PhabricatorFlagsUIEventListener' => 'applications/flag/events/PhabricatorFlagsUIEventListener.php', |     'PhabricatorFlagsUIEventListener' => 'applications/flag/events/PhabricatorFlagsUIEventListener.php', | ||||||
|     'PhabricatorFormExample' => 'applications/uiexample/examples/PhabricatorFormExample.php', |     'PhabricatorFormExample' => 'applications/uiexample/examples/PhabricatorFormExample.php', | ||||||
|  |     'PhabricatorFundApplication' => 'applications/fund/application/PhabricatorFundApplication.php', | ||||||
|     'PhabricatorGarbageCollector' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php', |     'PhabricatorGarbageCollector' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php', | ||||||
|     'PhabricatorGarbageCollectorConfigOptions' => 'applications/config/option/PhabricatorGarbageCollectorConfigOptions.php', |     'PhabricatorGarbageCollectorConfigOptions' => 'applications/config/option/PhabricatorGarbageCollectorConfigOptions.php', | ||||||
|     'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollectorDaemon.php', |     'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollectorDaemon.php', | ||||||
| @@ -3422,6 +3445,42 @@ phutil_register_library_map(array( | |||||||
|     'FlagDeleteConduitAPIMethod' => 'FlagConduitAPIMethod', |     'FlagDeleteConduitAPIMethod' => 'FlagConduitAPIMethod', | ||||||
|     'FlagEditConduitAPIMethod' => 'FlagConduitAPIMethod', |     'FlagEditConduitAPIMethod' => 'FlagConduitAPIMethod', | ||||||
|     'FlagQueryConduitAPIMethod' => 'FlagConduitAPIMethod', |     'FlagQueryConduitAPIMethod' => 'FlagConduitAPIMethod', | ||||||
|  |     'FundBacking' => array( | ||||||
|  |       'FundDAO', | ||||||
|  |       'PhabricatorPolicyInterface', | ||||||
|  |       'PhabricatorApplicationTransactionInterface', | ||||||
|  |     ), | ||||||
|  |     'FundBackingEditor' => 'PhabricatorApplicationTransactionEditor', | ||||||
|  |     'FundBackingPHIDType' => 'PhabricatorPHIDType', | ||||||
|  |     'FundBackingQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', | ||||||
|  |     'FundBackingTransaction' => 'PhabricatorApplicationTransaction', | ||||||
|  |     'FundBackingTransactionQuery' => 'PhabricatorApplicationTransactionQuery', | ||||||
|  |     'FundController' => 'PhabricatorController', | ||||||
|  |     'FundCreateInitiativesCapability' => 'PhabricatorPolicyCapability', | ||||||
|  |     'FundDAO' => 'PhabricatorLiskDAO', | ||||||
|  |     'FundDefaultViewCapability' => 'PhabricatorPolicyCapability', | ||||||
|  |     'FundInitiative' => array( | ||||||
|  |       'FundDAO', | ||||||
|  |       'PhabricatorPolicyInterface', | ||||||
|  |       'PhabricatorProjectInterface', | ||||||
|  |       'PhabricatorApplicationTransactionInterface', | ||||||
|  |       'PhabricatorSubscribableInterface', | ||||||
|  |       'PhabricatorMentionableInterface', | ||||||
|  |       'PhabricatorFlaggableInterface', | ||||||
|  |       'PhabricatorTokenReceiverInterface', | ||||||
|  |       'PhabricatorDestructibleInterface', | ||||||
|  |     ), | ||||||
|  |     'FundInitiativeCloseController' => 'FundController', | ||||||
|  |     'FundInitiativeEditController' => 'FundController', | ||||||
|  |     'FundInitiativeEditor' => 'PhabricatorApplicationTransactionEditor', | ||||||
|  |     'FundInitiativeListController' => 'FundController', | ||||||
|  |     'FundInitiativePHIDType' => 'PhabricatorPHIDType', | ||||||
|  |     'FundInitiativeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', | ||||||
|  |     'FundInitiativeRemarkupRule' => 'PhabricatorObjectRemarkupRule', | ||||||
|  |     'FundInitiativeSearchEngine' => 'PhabricatorApplicationSearchEngine', | ||||||
|  |     'FundInitiativeTransaction' => 'PhabricatorApplicationTransaction', | ||||||
|  |     'FundInitiativeTransactionQuery' => 'PhabricatorApplicationTransactionQuery', | ||||||
|  |     'FundInitiativeViewController' => 'FundController', | ||||||
|     'HarbormasterBuild' => array( |     'HarbormasterBuild' => array( | ||||||
|       'HarbormasterDAO', |       'HarbormasterDAO', | ||||||
|       'PhabricatorPolicyInterface', |       'PhabricatorPolicyInterface', | ||||||
| @@ -4465,6 +4524,7 @@ phutil_register_library_map(array( | |||||||
|     'PhabricatorFlagsApplication' => 'PhabricatorApplication', |     'PhabricatorFlagsApplication' => 'PhabricatorApplication', | ||||||
|     'PhabricatorFlagsUIEventListener' => 'PhabricatorEventListener', |     'PhabricatorFlagsUIEventListener' => 'PhabricatorEventListener', | ||||||
|     'PhabricatorFormExample' => 'PhabricatorUIExample', |     'PhabricatorFormExample' => 'PhabricatorUIExample', | ||||||
|  |     'PhabricatorFundApplication' => 'PhabricatorApplication', | ||||||
|     'PhabricatorGarbageCollector' => 'Phobject', |     'PhabricatorGarbageCollector' => 'Phobject', | ||||||
|     'PhabricatorGarbageCollectorConfigOptions' => 'PhabricatorApplicationConfigOptions', |     'PhabricatorGarbageCollectorConfigOptions' => 'PhabricatorApplicationConfigOptions', | ||||||
|     'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon', |     'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon', | ||||||
|   | |||||||
| @@ -0,0 +1,62 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class PhabricatorFundApplication extends PhabricatorApplication { | ||||||
|  |  | ||||||
|  |   public function getName() { | ||||||
|  |     return pht('Fund'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getBaseURI() { | ||||||
|  |     return '/fund/'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getShortDescription() { | ||||||
|  |     return pht('Donate'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getIconName() { | ||||||
|  |     return 'phund'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getTitleGlyph() { | ||||||
|  |     return "\xE2\x99\xA5"; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getApplicationGroup() { | ||||||
|  |     return self::GROUP_UTILITIES; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function isBeta() { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getRemarkupRules() { | ||||||
|  |     return array( | ||||||
|  |       new FundInitiativeRemarkupRule(), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getRoutes() { | ||||||
|  |     return array( | ||||||
|  |       '/I(?P<id>[1-9]\d*)' => 'FundInitiativeViewController', | ||||||
|  |       '/fund/' => array( | ||||||
|  |         '(?:query/(?P<queryKey>[^/]+)/)?' => 'FundInitiativeListController', | ||||||
|  |         'create/' => 'FundInitiativeEditController', | ||||||
|  |         'edit/(?:(?P<id>[^/]+)/)?' => 'FundInitiativeEditController', | ||||||
|  |         'close/(?P<id>[^/]+)/' => 'FundInitiativeCloseController', | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function getCustomCapabilities() { | ||||||
|  |     return array( | ||||||
|  |       FundDefaultViewCapability::CAPABILITY => array( | ||||||
|  |         'caption' => pht('Default view policy for newly created initiatives.'), | ||||||
|  |       ), | ||||||
|  |       FundCreateInitiativesCapability::CAPABILITY => array( | ||||||
|  |         'default' => PhabricatorPolicies::POLICY_ADMIN, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundCreateInitiativesCapability | ||||||
|  |   extends PhabricatorPolicyCapability { | ||||||
|  |  | ||||||
|  |   const CAPABILITY = 'fund.create'; | ||||||
|  |  | ||||||
|  |   public function getCapabilityName() { | ||||||
|  |     return pht('Can Create Initiatives'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function describeCapabilityRejection() { | ||||||
|  |     return pht('You do not have permission to create Fund initiatives.'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundDefaultViewCapability extends PhabricatorPolicyCapability { | ||||||
|  |  | ||||||
|  |   const CAPABILITY = 'fund.default.view'; | ||||||
|  |  | ||||||
|  |   public function getCapabilityName() { | ||||||
|  |     return pht('Default View Policy'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function shouldAllowPublicPolicySetting() { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								src/applications/fund/controller/FundController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/applications/fund/controller/FundController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | abstract class FundController extends PhabricatorController {} | ||||||
| @@ -0,0 +1,73 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundInitiativeCloseController | ||||||
|  |   extends FundController { | ||||||
|  |  | ||||||
|  |   private $id; | ||||||
|  |  | ||||||
|  |   public function willProcessRequest(array $data) { | ||||||
|  |     $this->id = $data['id']; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function processRequest() { | ||||||
|  |     $request = $this->getRequest(); | ||||||
|  |     $viewer = $request->getUser(); | ||||||
|  |  | ||||||
|  |     $initiative = id(new FundInitiativeQuery()) | ||||||
|  |       ->setViewer($viewer) | ||||||
|  |       ->withIDs(array($this->id)) | ||||||
|  |       ->requireCapabilities( | ||||||
|  |         array( | ||||||
|  |           PhabricatorPolicyCapability::CAN_VIEW, | ||||||
|  |           PhabricatorPolicyCapability::CAN_EDIT, | ||||||
|  |         )) | ||||||
|  |       ->executeOne(); | ||||||
|  |     if (!$initiative) { | ||||||
|  |       return new Aphront404Response(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $initiative_uri = '/'.$initiative->getMonogram(); | ||||||
|  |  | ||||||
|  |     $is_close = !$initiative->isClosed(); | ||||||
|  |  | ||||||
|  |     if ($request->isFormPost()) { | ||||||
|  |       $type_status = FundInitiativeTransaction::TYPE_STATUS; | ||||||
|  |  | ||||||
|  |       if ($is_close) { | ||||||
|  |         $new_status = FundInitiative::STATUS_CLOSED; | ||||||
|  |       } else { | ||||||
|  |         $new_status = FundInitiative::STATUS_OPEN; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       $xaction = id(new FundInitiativeTransaction()) | ||||||
|  |         ->setTransactionType($type_status) | ||||||
|  |         ->setNewValue($new_status); | ||||||
|  |  | ||||||
|  |       $editor = id(new FundInitiativeEditor()) | ||||||
|  |         ->setActor($viewer) | ||||||
|  |         ->setContentSourceFromRequest($request) | ||||||
|  |         ->setContinueOnMissingFields(true); | ||||||
|  |  | ||||||
|  |       $editor->applyTransactions($initiative, array($xaction)); | ||||||
|  |  | ||||||
|  |       return id(new AphrontRedirectResponse())->setURI($initiative_uri); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if ($is_close) { | ||||||
|  |       $title = pht('Close Initiative?'); | ||||||
|  |       $body = pht('Really close this initiative?'); | ||||||
|  |       $button_text = pht('Close Initiative'); | ||||||
|  |     } else { | ||||||
|  |       $title = pht('Reopen Initiative?'); | ||||||
|  |       $body = pht('Really reopen this initiative?'); | ||||||
|  |       $button_text = pht('Reopen Initiative'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $this->newDialog() | ||||||
|  |       ->setTitle($title) | ||||||
|  |       ->appendParagraph($body) | ||||||
|  |       ->addCancelButton($initiative_uri) | ||||||
|  |       ->addSubmitButton($button_text); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,191 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundInitiativeEditController | ||||||
|  |   extends FundController { | ||||||
|  |  | ||||||
|  |   private $id; | ||||||
|  |  | ||||||
|  |   public function willProcessRequest(array $data) { | ||||||
|  |     $this->id = idx($data, 'id'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function processRequest() { | ||||||
|  |     $request = $this->getRequest(); | ||||||
|  |     $viewer = $request->getUser(); | ||||||
|  |  | ||||||
|  |     if ($this->id) { | ||||||
|  |       $initiative = id(new FundInitiativeQuery()) | ||||||
|  |         ->setViewer($viewer) | ||||||
|  |         ->withIDs(array($this->id)) | ||||||
|  |         ->requireCapabilities( | ||||||
|  |           array( | ||||||
|  |             PhabricatorPolicyCapability::CAN_VIEW, | ||||||
|  |             PhabricatorPolicyCapability::CAN_EDIT, | ||||||
|  |           )) | ||||||
|  |         ->executeOne(); | ||||||
|  |       if (!$initiative) { | ||||||
|  |         return new Aphront404Response(); | ||||||
|  |       } | ||||||
|  |       $is_new = false; | ||||||
|  |     } else { | ||||||
|  |       $initiative = FundInitiative::initializeNewInitiative($viewer); | ||||||
|  |       $is_new = true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if ($is_new) { | ||||||
|  |       $title = pht('Create Initiative'); | ||||||
|  |       $button_text = pht('Create Initiative'); | ||||||
|  |       $cancel_uri = $this->getApplicationURI(); | ||||||
|  |     } else { | ||||||
|  |       $title = pht( | ||||||
|  |         'Edit %s %s', | ||||||
|  |         $initiative->getMonogram(), | ||||||
|  |         $initiative->getName()); | ||||||
|  |       $button_text = pht('Save Changes'); | ||||||
|  |       $cancel_uri = '/'.$initiative->getMonogram(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $e_name = true; | ||||||
|  |     $v_name = $initiative->getName(); | ||||||
|  |  | ||||||
|  |     $v_desc = $initiative->getDescription(); | ||||||
|  |  | ||||||
|  |     if ($is_new) { | ||||||
|  |       $v_projects = array(); | ||||||
|  |     } else { | ||||||
|  |       $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( | ||||||
|  |         $initiative->getPHID(), | ||||||
|  |         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); | ||||||
|  |       $v_projects = array_reverse($v_projects); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $validation_exception = null; | ||||||
|  |     if ($request->isFormPost()) { | ||||||
|  |       $v_name = $request->getStr('name'); | ||||||
|  |       $v_desc = $request->getStr('description'); | ||||||
|  |       $v_view = $request->getStr('viewPolicy'); | ||||||
|  |       $v_edit = $request->getStr('editPolicy'); | ||||||
|  |       $v_projects = $request->getArr('projects'); | ||||||
|  |  | ||||||
|  |       $type_name = FundInitiativeTransaction::TYPE_NAME; | ||||||
|  |       $type_desc = FundInitiativeTransaction::TYPE_DESCRIPTION; | ||||||
|  |       $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; | ||||||
|  |       $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; | ||||||
|  |  | ||||||
|  |       $xactions = array(); | ||||||
|  |  | ||||||
|  |       $xactions[] = id(new FundInitiativeTransaction()) | ||||||
|  |         ->setTransactionType($type_name) | ||||||
|  |         ->setNewValue($v_name); | ||||||
|  |  | ||||||
|  |       $xactions[] = id(new FundInitiativeTransaction()) | ||||||
|  |         ->setTransactionType($type_desc) | ||||||
|  |         ->setNewValue($v_desc); | ||||||
|  |  | ||||||
|  |       $xactions[] = id(new FundInitiativeTransaction()) | ||||||
|  |         ->setTransactionType($type_view) | ||||||
|  |         ->setNewValue($v_view); | ||||||
|  |  | ||||||
|  |       $xactions[] = id(new FundInitiativeTransaction()) | ||||||
|  |         ->setTransactionType($type_edit) | ||||||
|  |         ->setNewValue($v_edit); | ||||||
|  |  | ||||||
|  |       $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; | ||||||
|  |       $xactions[] = id(new FundInitiativeTransaction()) | ||||||
|  |         ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) | ||||||
|  |         ->setMetadataValue('edge:type', $proj_edge_type) | ||||||
|  |         ->setNewValue(array('=' => array_fuse($v_projects))); | ||||||
|  |  | ||||||
|  |       $editor = id(new FundInitiativeEditor()) | ||||||
|  |         ->setActor($viewer) | ||||||
|  |         ->setContentSourceFromRequest($request) | ||||||
|  |         ->setContinueOnNoEffect(true); | ||||||
|  |  | ||||||
|  |       try { | ||||||
|  |         $editor->applyTransactions($initiative, $xactions); | ||||||
|  |  | ||||||
|  |         return id(new AphrontRedirectResponse()) | ||||||
|  |           ->setURI('/'.$initiative->getMonogram()); | ||||||
|  |       } catch (PhabricatorApplicationTransactionValidationException $ex) { | ||||||
|  |         $validation_exception = $ex; | ||||||
|  |  | ||||||
|  |         $e_name = $ex->getShortMessage($type_name); | ||||||
|  |  | ||||||
|  |         $initiative->setViewPolicy($v_view); | ||||||
|  |         $initiative->setEditPolicy($v_edit); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $policies = id(new PhabricatorPolicyQuery()) | ||||||
|  |       ->setViewer($viewer) | ||||||
|  |       ->setObject($initiative) | ||||||
|  |       ->execute(); | ||||||
|  |  | ||||||
|  |     if ($v_projects) { | ||||||
|  |       $project_handles = $this->loadViewerHandles($v_projects); | ||||||
|  |     } else { | ||||||
|  |       $project_handles = array(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $form = id(new AphrontFormView()) | ||||||
|  |       ->setUser($viewer) | ||||||
|  |       ->appendChild( | ||||||
|  |         id(new AphrontFormTextControl()) | ||||||
|  |           ->setName('name') | ||||||
|  |           ->setLabel(pht('Name')) | ||||||
|  |           ->setValue($v_name) | ||||||
|  |           ->setError($e_name)) | ||||||
|  |       ->appendChild( | ||||||
|  |         id(new PhabricatorRemarkupControl()) | ||||||
|  |           ->setName('description') | ||||||
|  |           ->setLabel(pht('Description')) | ||||||
|  |           ->setValue($v_desc)) | ||||||
|  |       ->appendChild( | ||||||
|  |         id(new AphrontFormTokenizerControl()) | ||||||
|  |           ->setLabel(pht('Projects')) | ||||||
|  |           ->setName('projects') | ||||||
|  |           ->setValue($project_handles) | ||||||
|  |           ->setDatasource(new PhabricatorProjectDatasource())) | ||||||
|  |       ->appendChild( | ||||||
|  |         id(new AphrontFormPolicyControl()) | ||||||
|  |           ->setName('viewPolicy') | ||||||
|  |           ->setPolicyObject($initiative) | ||||||
|  |           ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) | ||||||
|  |           ->setPolicies($policies)) | ||||||
|  |       ->appendChild( | ||||||
|  |         id(new AphrontFormPolicyControl()) | ||||||
|  |           ->setName('editPolicy') | ||||||
|  |           ->setPolicyObject($initiative) | ||||||
|  |           ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) | ||||||
|  |           ->setPolicies($policies)) | ||||||
|  |       ->appendChild( | ||||||
|  |         id(new AphrontFormSubmitControl()) | ||||||
|  |           ->setValue($button_text) | ||||||
|  |           ->addCancelButton($cancel_uri)); | ||||||
|  |  | ||||||
|  |     $crumbs = $this->buildApplicationCrumbs(); | ||||||
|  |     if ($is_new) { | ||||||
|  |       $crumbs->addTextCrumb(pht('Create Initiative')); | ||||||
|  |     } else { | ||||||
|  |       $crumbs->addTextCrumb( | ||||||
|  |         $initiative->getMonogram(), | ||||||
|  |         '/'.$initiative->getMonogram()); | ||||||
|  |       $crumbs->addTextCrumb(pht('Edit')); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $box = id(new PHUIObjectBoxView()) | ||||||
|  |       ->setValidationException($validation_exception) | ||||||
|  |       ->setHeaderText($title) | ||||||
|  |       ->appendChild($form); | ||||||
|  |  | ||||||
|  |     return $this->buildApplicationPage( | ||||||
|  |       array( | ||||||
|  |         $crumbs, | ||||||
|  |         $box, | ||||||
|  |       ), | ||||||
|  |       array( | ||||||
|  |         'title' => $title, | ||||||
|  |       )); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,53 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundInitiativeListController | ||||||
|  |   extends FundController { | ||||||
|  |  | ||||||
|  |   private $queryKey; | ||||||
|  |  | ||||||
|  |   public function willProcessRequest(array $data) { | ||||||
|  |     $this->queryKey = idx($data, 'queryKey'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function processRequest() { | ||||||
|  |     $request = $this->getRequest(); | ||||||
|  |     $controller = id(new PhabricatorApplicationSearchController($request)) | ||||||
|  |       ->setQueryKey($this->queryKey) | ||||||
|  |       ->setSearchEngine(new FundInitiativeSearchEngine()) | ||||||
|  |       ->setNavigation($this->buildSideNavView()); | ||||||
|  |  | ||||||
|  |     return $this->delegateToController($controller); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function buildSideNavView() { | ||||||
|  |     $user = $this->getRequest()->getUser(); | ||||||
|  |  | ||||||
|  |     $nav = new AphrontSideNavFilterView(); | ||||||
|  |     $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); | ||||||
|  |  | ||||||
|  |     id(new FundInitiativeSearchEngine()) | ||||||
|  |       ->setViewer($user) | ||||||
|  |       ->addNavigationItems($nav->getMenu()); | ||||||
|  |     $nav->selectFilter(null); | ||||||
|  |  | ||||||
|  |     return $nav; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function buildApplicationCrumbs() { | ||||||
|  |     $crumbs = parent::buildApplicationCrumbs(); | ||||||
|  |  | ||||||
|  |     $can_create = $this->hasApplicationCapability( | ||||||
|  |       FundCreateInitiativesCapability::CAPABILITY); | ||||||
|  |  | ||||||
|  |     $crumbs->addAction( | ||||||
|  |       id(new PHUIListItemView()) | ||||||
|  |         ->setName(pht('Create Initiative')) | ||||||
|  |         ->setHref($this->getApplicationURI('create/')) | ||||||
|  |         ->setIcon('fa-plus-square') | ||||||
|  |         ->setDisabled(!$can_create) | ||||||
|  |         ->setWorkflow(!$can_create)); | ||||||
|  |  | ||||||
|  |     return $crumbs; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,149 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundInitiativeViewController | ||||||
|  |   extends FundController { | ||||||
|  |  | ||||||
|  |   private $id; | ||||||
|  |  | ||||||
|  |   public function willProcessRequest(array $data) { | ||||||
|  |     $this->id = $data['id']; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function processRequest() { | ||||||
|  |     $request = $this->getRequest(); | ||||||
|  |     $viewer = $request->getUser(); | ||||||
|  |  | ||||||
|  |     $initiative = id(new FundInitiativeQuery()) | ||||||
|  |       ->setViewer($viewer) | ||||||
|  |       ->withIDs(array($this->id)) | ||||||
|  |       ->executeOne(); | ||||||
|  |     if (!$initiative) { | ||||||
|  |       return new Aphront404Response(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $crumbs = $this->buildApplicationCrumbs(); | ||||||
|  |     $crumbs->addTextCrumb($initiative->getMonogram()); | ||||||
|  |  | ||||||
|  |     $title = pht( | ||||||
|  |       '%s %s', | ||||||
|  |       $initiative->getMonogram(), | ||||||
|  |       $initiative->getName()); | ||||||
|  |  | ||||||
|  |     if ($initiative->isClosed()) { | ||||||
|  |       $status_icon = 'fa-times'; | ||||||
|  |       $status_color = 'bluegrey'; | ||||||
|  |     } else { | ||||||
|  |       $status_icon = 'fa-check'; | ||||||
|  |       $status_color = 'bluegrey'; | ||||||
|  |     } | ||||||
|  |     $status_name = idx( | ||||||
|  |       FundInitiative::getStatusNameMap(), | ||||||
|  |       $initiative->getStatus()); | ||||||
|  |  | ||||||
|  |     $header = id(new PHUIHeaderView()) | ||||||
|  |       ->setObjectName($initiative->getMonogram()) | ||||||
|  |       ->setHeader($initiative->getName()) | ||||||
|  |       ->setUser($viewer) | ||||||
|  |       ->setPolicyObject($initiative) | ||||||
|  |       ->setStatus($status_icon, $status_color, $status_name); | ||||||
|  |  | ||||||
|  |     $properties = $this->buildPropertyListView($initiative); | ||||||
|  |     $actions = $this->buildActionListView($initiative); | ||||||
|  |     $properties->setActionList($actions); | ||||||
|  |  | ||||||
|  |     $box = id(new PHUIObjectBoxView()) | ||||||
|  |       ->setHeader($header) | ||||||
|  |       ->appendChild($properties); | ||||||
|  |  | ||||||
|  |     $xactions = id(new FundInitiativeTransactionQuery()) | ||||||
|  |       ->setViewer($viewer) | ||||||
|  |       ->withObjectPHIDs(array($initiative->getPHID())) | ||||||
|  |       ->execute(); | ||||||
|  |  | ||||||
|  |     $timeline = id(new PhabricatorApplicationTransactionView()) | ||||||
|  |       ->setUser($viewer) | ||||||
|  |       ->setObjectPHID($initiative->getPHID()) | ||||||
|  |       ->setTransactions($xactions); | ||||||
|  |  | ||||||
|  |     return $this->buildApplicationPage( | ||||||
|  |       array( | ||||||
|  |         $crumbs, | ||||||
|  |         $box, | ||||||
|  |         $timeline, | ||||||
|  |       ), | ||||||
|  |       array( | ||||||
|  |         'title' => $title, | ||||||
|  |       )); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function buildPropertyListView(FundInitiative $initiative) { | ||||||
|  |     $viewer = $this->getRequest()->getUser(); | ||||||
|  |  | ||||||
|  |     $view = id(new PHUIPropertyListView()) | ||||||
|  |       ->setUser($viewer) | ||||||
|  |       ->setObject($initiative); | ||||||
|  |  | ||||||
|  |     $owner_phid = $initiative->getOwnerPHID(); | ||||||
|  |     $this->loadHandles(array($owner_phid)); | ||||||
|  |  | ||||||
|  |     $view->addProperty( | ||||||
|  |       pht('Owner'), | ||||||
|  |       $this->getHandle($owner_phid)->renderLink()); | ||||||
|  |  | ||||||
|  |     $view->invokeWillRenderEvent(); | ||||||
|  |  | ||||||
|  |     $description = $initiative->getDescription(); | ||||||
|  |     if (strlen($description)) { | ||||||
|  |       $description = PhabricatorMarkupEngine::renderOneObject( | ||||||
|  |         id(new PhabricatorMarkupOneOff())->setContent($description), | ||||||
|  |         'default', | ||||||
|  |         $viewer); | ||||||
|  |  | ||||||
|  |       $view->addSectionHeader(pht('Description')); | ||||||
|  |       $view->addTextContent($description); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $view; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function buildActionListView(FundInitiative $initiative) { | ||||||
|  |     $viewer = $this->getRequest()->getUser(); | ||||||
|  |     $id = $initiative->getID(); | ||||||
|  |  | ||||||
|  |     $can_edit = PhabricatorPolicyFilter::hasCapability( | ||||||
|  |       $viewer, | ||||||
|  |       $initiative, | ||||||
|  |       PhabricatorPolicyCapability::CAN_EDIT); | ||||||
|  |  | ||||||
|  |     $view = id(new PhabricatorActionListView()) | ||||||
|  |       ->setUser($viewer) | ||||||
|  |       ->setObject($initiative); | ||||||
|  |  | ||||||
|  |     $view->addAction( | ||||||
|  |       id(new PhabricatorActionView()) | ||||||
|  |         ->setName(pht('Edit Initiative')) | ||||||
|  |         ->setIcon('fa-pencil') | ||||||
|  |         ->setDisabled(!$can_edit) | ||||||
|  |         ->setWorkflow(!$can_edit) | ||||||
|  |         ->setHref($this->getApplicationURI("/edit/{$id}/"))); | ||||||
|  |  | ||||||
|  |     if ($initiative->isClosed()) { | ||||||
|  |       $close_name = pht('Reopen Initiative'); | ||||||
|  |       $close_icon = 'fa-check'; | ||||||
|  |     } else { | ||||||
|  |       $close_name = pht('Close Initiative'); | ||||||
|  |       $close_icon = 'fa-times'; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $view->addAction( | ||||||
|  |       id(new PhabricatorActionView()) | ||||||
|  |         ->setName($close_name) | ||||||
|  |         ->setIcon($close_icon) | ||||||
|  |         ->setDisabled(!$can_edit) | ||||||
|  |         ->setWorkflow(true) | ||||||
|  |         ->setHref($this->getApplicationURI("/close/{$id}/"))); | ||||||
|  |  | ||||||
|  |     return $view; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								src/applications/fund/editor/FundBackingEditor.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/applications/fund/editor/FundBackingEditor.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundBackingEditor | ||||||
|  |   extends PhabricatorApplicationTransactionEditor { | ||||||
|  |  | ||||||
|  |   public function getEditorApplicationClass() { | ||||||
|  |     return 'PhabricatorFundApplication'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getEditorObjectsDescription() { | ||||||
|  |     return pht('Fund Backing'); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										123
									
								
								src/applications/fund/editor/FundInitiativeEditor.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/applications/fund/editor/FundInitiativeEditor.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundInitiativeEditor | ||||||
|  |   extends PhabricatorApplicationTransactionEditor { | ||||||
|  |  | ||||||
|  |   public function getEditorApplicationClass() { | ||||||
|  |     return 'PhabricatorFundApplication'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getEditorObjectsDescription() { | ||||||
|  |     return pht('Fund Initiatives'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getTransactionTypes() { | ||||||
|  |     $types = parent::getTransactionTypes(); | ||||||
|  |  | ||||||
|  |     $types[] = FundInitiativeTransaction::TYPE_NAME; | ||||||
|  |     $types[] = FundInitiativeTransaction::TYPE_DESCRIPTION; | ||||||
|  |     $types[] = FundInitiativeTransaction::TYPE_STATUS; | ||||||
|  |     $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; | ||||||
|  |     $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; | ||||||
|  |  | ||||||
|  |     return $types; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function getCustomTransactionOldValue( | ||||||
|  |     PhabricatorLiskDAO $object, | ||||||
|  |     PhabricatorApplicationTransaction $xaction) { | ||||||
|  |     switch ($xaction->getTransactionType()) { | ||||||
|  |       case FundInitiativeTransaction::TYPE_NAME: | ||||||
|  |         return $object->getName(); | ||||||
|  |       case FundInitiativeTransaction::TYPE_DESCRIPTION: | ||||||
|  |         return $object->getDescription(); | ||||||
|  |       case FundInitiativeTransaction::TYPE_STATUS: | ||||||
|  |         return $object->getStatus(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return parent::getCustomTransactionOldValue($object, $xaction); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function getCustomTransactionNewValue( | ||||||
|  |     PhabricatorLiskDAO $object, | ||||||
|  |     PhabricatorApplicationTransaction $xaction) { | ||||||
|  |  | ||||||
|  |     switch ($xaction->getTransactionType()) { | ||||||
|  |       case FundInitiativeTransaction::TYPE_NAME: | ||||||
|  |       case FundInitiativeTransaction::TYPE_DESCRIPTION: | ||||||
|  |       case FundInitiativeTransaction::TYPE_STATUS: | ||||||
|  |         return $xaction->getNewValue(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return parent::getCustomTransactionNewValue($object, $xaction); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function applyCustomInternalTransaction( | ||||||
|  |     PhabricatorLiskDAO $object, | ||||||
|  |     PhabricatorApplicationTransaction $xaction) { | ||||||
|  |  | ||||||
|  |     switch ($xaction->getTransactionType()) { | ||||||
|  |       case FundInitiativeTransaction::TYPE_NAME: | ||||||
|  |         $object->setName($xaction->getNewValue()); | ||||||
|  |         return; | ||||||
|  |       case FundInitiativeTransaction::TYPE_DESCRIPTION: | ||||||
|  |         $object->setDescription($xaction->getNewValue()); | ||||||
|  |         return; | ||||||
|  |       case FundInitiativeTransaction::TYPE_STATUS: | ||||||
|  |         $object->setStatus($xaction->getNewValue()); | ||||||
|  |         return; | ||||||
|  |       case PhabricatorTransactions::TYPE_SUBSCRIBERS: | ||||||
|  |       case PhabricatorTransactions::TYPE_EDGE: | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return parent::applyCustomInternalTransaction($object, $xaction); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function applyCustomExternalTransaction( | ||||||
|  |     PhabricatorLiskDAO $object, | ||||||
|  |     PhabricatorApplicationTransaction $xaction) { | ||||||
|  |  | ||||||
|  |     switch ($xaction->getTransactionType()) { | ||||||
|  |       case FundInitiativeTransaction::TYPE_NAME: | ||||||
|  |       case FundInitiativeTransaction::TYPE_DESCRIPTION: | ||||||
|  |       case FundInitiativeTransaction::TYPE_STATUS: | ||||||
|  |       case PhabricatorTransactions::TYPE_SUBSCRIBERS: | ||||||
|  |       case PhabricatorTransactions::TYPE_EDGE: | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return parent::applyCustomExternalTransaction($object, $xaction); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function validateTransaction( | ||||||
|  |     PhabricatorLiskDAO $object, | ||||||
|  |     $type, | ||||||
|  |     array $xactions) { | ||||||
|  |  | ||||||
|  |     $errors = parent::validateTransaction($object, $type, $xactions); | ||||||
|  |  | ||||||
|  |     switch ($type) { | ||||||
|  |       case FundInitiativeTransaction::TYPE_NAME: | ||||||
|  |         $missing = $this->validateIsEmptyTextField( | ||||||
|  |           $object->getName(), | ||||||
|  |           $xactions); | ||||||
|  |  | ||||||
|  |         if ($missing) { | ||||||
|  |           $error = new PhabricatorApplicationTransactionValidationError( | ||||||
|  |             $type, | ||||||
|  |             pht('Required'), | ||||||
|  |             pht('Initiative name is required.'), | ||||||
|  |             nonempty(last($xactions), null)); | ||||||
|  |  | ||||||
|  |           $error->setIsMissingFieldError(true); | ||||||
|  |           $errors[] = $error; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $errors; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								src/applications/fund/phid/FundBackingPHIDType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/applications/fund/phid/FundBackingPHIDType.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundBackingPHIDType extends PhabricatorPHIDType { | ||||||
|  |  | ||||||
|  |   const TYPECONST = 'FBAK'; | ||||||
|  |  | ||||||
|  |   public function getTypeName() { | ||||||
|  |     return pht('Variable'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function newObject() { | ||||||
|  |     return new FundInitiative(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function buildQueryForObjects( | ||||||
|  |     PhabricatorObjectQuery $query, | ||||||
|  |     array $phids) { | ||||||
|  |  | ||||||
|  |     return id(new FundInitiativeQuery()) | ||||||
|  |       ->withPHIDs($phids); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function loadHandles( | ||||||
|  |     PhabricatorHandleQuery $query, | ||||||
|  |     array $handles, | ||||||
|  |     array $objects) { | ||||||
|  |  | ||||||
|  |     foreach ($handles as $phid => $handle) { | ||||||
|  |       $initiative = $objects[$phid]; | ||||||
|  |  | ||||||
|  |       $id = $initiative->getID(); | ||||||
|  |       $monogram = $initiative->getMonogram(); | ||||||
|  |       $name = $initiative->getName(); | ||||||
|  |  | ||||||
|  |       $handle->setName($name); | ||||||
|  |       $handle->setFullName("{$monogram} {$name}"); | ||||||
|  |       $handle->setURI("/fund/view/{$id}/"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										74
									
								
								src/applications/fund/phid/FundInitiativePHIDType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/applications/fund/phid/FundInitiativePHIDType.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundInitiativePHIDType extends PhabricatorPHIDType { | ||||||
|  |  | ||||||
|  |   const TYPECONST = 'FITV'; | ||||||
|  |  | ||||||
|  |   public function getTypeName() { | ||||||
|  |     return pht('Initiative'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function newObject() { | ||||||
|  |     return new FundInitiative(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function buildQueryForObjects( | ||||||
|  |     PhabricatorObjectQuery $query, | ||||||
|  |     array $phids) { | ||||||
|  |  | ||||||
|  |     return id(new FundInitiativeQuery()) | ||||||
|  |       ->withPHIDs($phids); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function loadHandles( | ||||||
|  |     PhabricatorHandleQuery $query, | ||||||
|  |     array $handles, | ||||||
|  |     array $objects) { | ||||||
|  |  | ||||||
|  |     foreach ($handles as $phid => $handle) { | ||||||
|  |       $initiative = $objects[$phid]; | ||||||
|  |  | ||||||
|  |       $id = $initiative->getID(); | ||||||
|  |       $monogram = $initiative->getMonogram(); | ||||||
|  |       $name = $initiative->getName(); | ||||||
|  |  | ||||||
|  |       if ($initiative->isClosed()) { | ||||||
|  |         $handle->setStatus(PhabricatorObjectHandleStatus::STATUS_CLOSED); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       $handle->setName($name); | ||||||
|  |       $handle->setFullName("{$monogram} {$name}"); | ||||||
|  |       $handle->setURI("/I{$id}"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function canLoadNamedObject($name) { | ||||||
|  |     return preg_match('/^I\d*[1-9]\d*$/i', $name); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function loadNamedObjects( | ||||||
|  |     PhabricatorObjectQuery $query, | ||||||
|  |     array $names) { | ||||||
|  |  | ||||||
|  |     $id_map = array(); | ||||||
|  |     foreach ($names as $name) { | ||||||
|  |       $id = (int)substr($name, 1); | ||||||
|  |       $id_map[$id][] = $name; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $objects = id(new FundInitiativeQuery()) | ||||||
|  |       ->setViewer($query->getViewer()) | ||||||
|  |       ->withIDs(array_keys($id_map)) | ||||||
|  |       ->execute(); | ||||||
|  |  | ||||||
|  |     $results = array(); | ||||||
|  |     foreach ($objects as $id => $object) { | ||||||
|  |       foreach (idx($id_map, $id, array()) as $name) { | ||||||
|  |         $results[$name] = $object; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $results; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								src/applications/fund/query/FundBackingQuery.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/applications/fund/query/FundBackingQuery.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundBackingQuery | ||||||
|  |   extends PhabricatorCursorPagedPolicyAwareQuery { | ||||||
|  |  | ||||||
|  |   private $ids; | ||||||
|  |   private $phids; | ||||||
|  |  | ||||||
|  |   public function withIDs(array $ids) { | ||||||
|  |     $this->ids = $ids; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function withPHIDs(array $phids) { | ||||||
|  |     $this->phids = $phids; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function loadPage() { | ||||||
|  |     $table = new FundBacking(); | ||||||
|  |     $conn_r = $table->establishConnection('r'); | ||||||
|  |  | ||||||
|  |     $rows = queryfx_all( | ||||||
|  |       $conn_r, | ||||||
|  |       'SELECT * FROM %T %Q %Q %Q', | ||||||
|  |       $table->getTableName(), | ||||||
|  |       $this->buildWhereClause($conn_r), | ||||||
|  |       $this->buildOrderClause($conn_r), | ||||||
|  |       $this->buildLimitClause($conn_r)); | ||||||
|  |  | ||||||
|  |     return $table->loadAllFromArray($rows); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function buildWhereClause(AphrontDatabaseConnection $conn_r) { | ||||||
|  |     $where = array(); | ||||||
|  |  | ||||||
|  |     $where[] = $this->buildPagingClause($conn_r); | ||||||
|  |  | ||||||
|  |     if ($this->ids !== null) { | ||||||
|  |       $where[] = qsprintf( | ||||||
|  |         $conn_r, | ||||||
|  |         'id IN (%Ld)', | ||||||
|  |         $this->ids); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if ($this->phids !== null) { | ||||||
|  |       $where[] = qsprintf( | ||||||
|  |         $conn_r, | ||||||
|  |         'phid IN (%Ls)', | ||||||
|  |         $this->phids); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $this->formatWhereClause($where); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getQueryApplicationClass() { | ||||||
|  |     return 'PhabricatorFundApplication'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								src/applications/fund/query/FundBackingTransactionQuery.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/applications/fund/query/FundBackingTransactionQuery.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundBackingTransactionQuery | ||||||
|  |   extends PhabricatorApplicationTransactionQuery { | ||||||
|  |  | ||||||
|  |   public function getTemplateApplicationTransaction() { | ||||||
|  |     return new FundBackingTransaction(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										116
									
								
								src/applications/fund/query/FundInitiativeQuery.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/applications/fund/query/FundInitiativeQuery.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundInitiativeQuery | ||||||
|  |   extends PhabricatorCursorPagedPolicyAwareQuery { | ||||||
|  |  | ||||||
|  |   private $ids; | ||||||
|  |   private $phids; | ||||||
|  |   private $ownerPHIDs; | ||||||
|  |   private $statuses; | ||||||
|  |  | ||||||
|  |   private $needProjectPHIDs; | ||||||
|  |  | ||||||
|  |   public function withIDs(array $ids) { | ||||||
|  |     $this->ids = $ids; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function withPHIDs(array $phids) { | ||||||
|  |     $this->phids = $phids; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function withOwnerPHIDs(array $phids) { | ||||||
|  |     $this->ownerPHIDs = $phids; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function withStatuses(array $statuses) { | ||||||
|  |     $this->statuses = $statuses; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function needProjectPHIDs($need) { | ||||||
|  |     $this->needProjectPHIDs = $need; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function loadPage() { | ||||||
|  |     $table = new FundInitiative(); | ||||||
|  |     $conn_r = $table->establishConnection('r'); | ||||||
|  |  | ||||||
|  |     $rows = queryfx_all( | ||||||
|  |       $conn_r, | ||||||
|  |       'SELECT * FROM %T %Q %Q %Q', | ||||||
|  |       $table->getTableName(), | ||||||
|  |       $this->buildWhereClause($conn_r), | ||||||
|  |       $this->buildOrderClause($conn_r), | ||||||
|  |       $this->buildLimitClause($conn_r)); | ||||||
|  |  | ||||||
|  |     return $table->loadAllFromArray($rows); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function didFilterPage(array $initiatives) { | ||||||
|  |  | ||||||
|  |     if ($this->needProjectPHIDs) { | ||||||
|  |       $edge_query = id(new PhabricatorEdgeQuery()) | ||||||
|  |         ->withSourcePHIDs(mpull($initiatives, 'getPHID')) | ||||||
|  |         ->withEdgeTypes( | ||||||
|  |           array( | ||||||
|  |             PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, | ||||||
|  |           )); | ||||||
|  |       $edge_query->execute(); | ||||||
|  |  | ||||||
|  |       foreach ($initiatives as $initiative) { | ||||||
|  |         $phids = $edge_query->getDestinationPHIDs( | ||||||
|  |           array( | ||||||
|  |             $initiative->getPHID(), | ||||||
|  |           )); | ||||||
|  |         $initiative->attachProjectPHIDs($phids); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $initiatives; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function buildWhereClause(AphrontDatabaseConnection $conn_r) { | ||||||
|  |     $where = array(); | ||||||
|  |  | ||||||
|  |     $where[] = $this->buildPagingClause($conn_r); | ||||||
|  |  | ||||||
|  |     if ($this->ids !== null) { | ||||||
|  |       $where[] = qsprintf( | ||||||
|  |         $conn_r, | ||||||
|  |         'id IN (%Ld)', | ||||||
|  |         $this->ids); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if ($this->phids !== null) { | ||||||
|  |       $where[] = qsprintf( | ||||||
|  |         $conn_r, | ||||||
|  |         'phid IN (%Ls)', | ||||||
|  |         $this->phids); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if ($this->ownerPHIDs !== null) { | ||||||
|  |       $where[] = qsprintf( | ||||||
|  |         $conn_r, | ||||||
|  |         'ownerPHID IN (%Ls)', | ||||||
|  |         $this->ownerPHIDs); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if ($this->statuses !== null) { | ||||||
|  |       $where[] = qsprintf( | ||||||
|  |         $conn_r, | ||||||
|  |         'status IN (%Ls)', | ||||||
|  |         $this->statuses); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $this->formatWhereClause($where); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getQueryApplicationClass() { | ||||||
|  |     return 'PhabricatorFundApplication'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										180
									
								
								src/applications/fund/query/FundInitiativeSearchEngine.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								src/applications/fund/query/FundInitiativeSearchEngine.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundInitiativeSearchEngine | ||||||
|  |   extends PhabricatorApplicationSearchEngine { | ||||||
|  |  | ||||||
|  |   public function getResultTypeDescription() { | ||||||
|  |     return pht('Fund Initiatives'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getApplicationClassName() { | ||||||
|  |     return 'PhabricatorFundApplication'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function buildSavedQueryFromRequest(AphrontRequest $request) { | ||||||
|  |     $saved = new PhabricatorSavedQuery(); | ||||||
|  |  | ||||||
|  |     $saved->setParameter( | ||||||
|  |       'ownerPHIDs', | ||||||
|  |       $this->readUsersFromRequest($request, 'owners')); | ||||||
|  |  | ||||||
|  |     $saved->setParameter( | ||||||
|  |       'statuses', | ||||||
|  |       $this->readListFromRequest($request, 'statuses')); | ||||||
|  |  | ||||||
|  |     return $saved; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { | ||||||
|  |     $query = id(new FundInitiativeQuery()) | ||||||
|  |       ->needProjectPHIDs(true); | ||||||
|  |  | ||||||
|  |     $owner_phids = $saved->getParameter('ownerPHIDs'); | ||||||
|  |     if ($owner_phids) { | ||||||
|  |       $query->withOwnerPHIDs($owner_phids); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $statuses = $saved->getParameter('statuses'); | ||||||
|  |     if ($statuses) { | ||||||
|  |       $query->withStatuses($statuses); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $query; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function buildSearchForm( | ||||||
|  |     AphrontFormView $form, | ||||||
|  |     PhabricatorSavedQuery $saved) { | ||||||
|  |  | ||||||
|  |     $statuses = $saved->getParameter('statuses', array()); | ||||||
|  |     $statuses = array_fuse($statuses); | ||||||
|  |  | ||||||
|  |     $owner_phids = $saved->getParameter('ownerPHIDs', array()); | ||||||
|  |  | ||||||
|  |     $all_phids = array_mergev( | ||||||
|  |       array( | ||||||
|  |         $owner_phids, | ||||||
|  |       )); | ||||||
|  |  | ||||||
|  |     $handles = id(new PhabricatorHandleQuery()) | ||||||
|  |       ->setViewer($this->requireViewer()) | ||||||
|  |       ->withPHIDs($all_phids) | ||||||
|  |       ->execute(); | ||||||
|  |  | ||||||
|  |     $status_map = FundInitiative::getStatusNameMap(); | ||||||
|  |     $status_control = id(new AphrontFormCheckboxControl()) | ||||||
|  |       ->setLabel(pht('Statuses')); | ||||||
|  |     foreach ($status_map as $status => $name) { | ||||||
|  |       $status_control->addCheckbox( | ||||||
|  |         'statuses[]', | ||||||
|  |         $status, | ||||||
|  |         $name, | ||||||
|  |         isset($statuses[$status])); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $form | ||||||
|  |       ->appendChild( | ||||||
|  |         id(new AphrontFormTokenizerControl()) | ||||||
|  |           ->setLabel(pht('Owners')) | ||||||
|  |           ->setName('owners') | ||||||
|  |           ->setDatasource(new PhabricatorPeopleDatasource()) | ||||||
|  |           ->setValue(array_select_keys($handles, $owner_phids))) | ||||||
|  |       ->appendChild($status_control); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function getURI($path) { | ||||||
|  |     return '/fund/'.$path; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getBuiltinQueryNames() { | ||||||
|  |     $names = array(); | ||||||
|  |  | ||||||
|  |     $names['open'] = pht('Open Initiatives'); | ||||||
|  |     if ($this->requireViewer()->isLoggedIn()) { | ||||||
|  |       $names['owned'] = pht('Owned Initiatives'); | ||||||
|  |     } | ||||||
|  |     $names['all'] = pht('All Initiatives'); | ||||||
|  |  | ||||||
|  |     return $names; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function buildSavedQueryFromBuiltin($query_key) { | ||||||
|  |     $query = $this->newSavedQuery(); | ||||||
|  |     $query->setQueryKey($query_key); | ||||||
|  |  | ||||||
|  |     switch ($query_key) { | ||||||
|  |       case 'all': | ||||||
|  |         return $query; | ||||||
|  |       case 'owned': | ||||||
|  |         return $query->setParameter( | ||||||
|  |           'ownerPHIDs', | ||||||
|  |           array( | ||||||
|  |             $this->requireViewer()->getPHID(), | ||||||
|  |           )); | ||||||
|  |       case 'open': | ||||||
|  |         return $query->setParameter( | ||||||
|  |           'statuses', | ||||||
|  |           array( | ||||||
|  |             FundInitiative::STATUS_OPEN, | ||||||
|  |           )); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return parent::buildSavedQueryFromBuiltin($query_key); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function getRequiredHandlePHIDsForResultList( | ||||||
|  |     array $initiatives, | ||||||
|  |     PhabricatorSavedQuery $query) { | ||||||
|  |  | ||||||
|  |     $phids = array(); | ||||||
|  |     foreach ($initiatives as $initiative) { | ||||||
|  |       $phids[] = $initiative->getOwnerPHID(); | ||||||
|  |       foreach ($initiative->getProjectPHIDs() as $project_phid) { | ||||||
|  |         $phids[] = $project_phid; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $phids; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function renderResultList( | ||||||
|  |     array $initiatives, | ||||||
|  |     PhabricatorSavedQuery $query, | ||||||
|  |     array $handles) { | ||||||
|  |     assert_instances_of($initiatives, 'FundInitiative'); | ||||||
|  |  | ||||||
|  |     $viewer = $this->requireViewer(); | ||||||
|  |  | ||||||
|  |     $list = id(new PHUIObjectItemListView()); | ||||||
|  |     foreach ($initiatives as $initiative) { | ||||||
|  |       $owner_handle = $handles[$initiative->getOwnerPHID()]; | ||||||
|  |  | ||||||
|  |       $item = id(new PHUIObjectItemView()) | ||||||
|  |         ->setObjectName($initiative->getMonogram()) | ||||||
|  |         ->setHeader($initiative->getName()) | ||||||
|  |         ->setHref('/'.$initiative->getMonogram()) | ||||||
|  |         ->addByline(pht('Owner: %s', $owner_handle->renderLink())); | ||||||
|  |  | ||||||
|  |       if ($initiative->isClosed()) { | ||||||
|  |         $item->setDisabled(true); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       $project_handles = array_select_keys( | ||||||
|  |         $handles, | ||||||
|  |         $initiative->getProjectPHIDs()); | ||||||
|  |       if ($project_handles) { | ||||||
|  |         $item->addAttribute( | ||||||
|  |           id(new PHUIHandleTagListView()) | ||||||
|  |             ->setLimit(4) | ||||||
|  |             ->setSlim(true) | ||||||
|  |             ->setHandles($project_handles)); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       $list->addItem($item); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     return $list; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,10 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundInitiativeTransactionQuery | ||||||
|  |   extends PhabricatorApplicationTransactionQuery { | ||||||
|  |  | ||||||
|  |   public function getTemplateApplicationTransaction() { | ||||||
|  |     return new FundInitiativeTransaction(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundInitiativeRemarkupRule extends PhabricatorObjectRemarkupRule { | ||||||
|  |  | ||||||
|  |   protected function getObjectNamePrefix() { | ||||||
|  |     return 'I'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function loadObjects(array $ids) { | ||||||
|  |     $viewer = $this->getEngine()->getConfig('viewer'); | ||||||
|  |  | ||||||
|  |     return id(new FundInitiativeQuery()) | ||||||
|  |       ->setViewer($viewer) | ||||||
|  |       ->withIDs($ids) | ||||||
|  |       ->execute(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								src/applications/fund/storage/FundBacking.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/applications/fund/storage/FundBacking.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundBacking extends FundDAO | ||||||
|  |   implements | ||||||
|  |     PhabricatorPolicyInterface, | ||||||
|  |     PhabricatorApplicationTransactionInterface { | ||||||
|  |  | ||||||
|  |   protected $initiativePHID; | ||||||
|  |   protected $backerPHID; | ||||||
|  |   protected $purchasePHID; | ||||||
|  |   protected $amountInCents; | ||||||
|  |   protected $status; | ||||||
|  |   protected $properties = array(); | ||||||
|  |  | ||||||
|  |   private $initiative = self::ATTACHABLE; | ||||||
|  |  | ||||||
|  |   public function getConfiguration() { | ||||||
|  |     return array( | ||||||
|  |       self::CONFIG_AUX_PHID => true, | ||||||
|  |     ) + parent::getConfiguration(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function generatePHID() { | ||||||
|  |     return PhabricatorPHID::generateNewPHID(FundBackingPHIDType::TYPECONST); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getProperty($key, $default = null) { | ||||||
|  |     return idx($this->properties, $key, $default); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function setProperty($key, $value) { | ||||||
|  |     $this->properties[$key] = $value; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /* -(  PhabricatorPolicyInterface  )----------------------------------------- */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   public function getCapabilities() { | ||||||
|  |     return array( | ||||||
|  |       PhabricatorPolicyCapability::CAN_VIEW, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getPolicy($capability) { | ||||||
|  |     switch ($capability) { | ||||||
|  |       case PhabricatorPolicyCapability::CAN_VIEW: | ||||||
|  |         // If we have the initiative, use the initiative's policy. | ||||||
|  |         // Otherwise, return NOONE. This allows the backer to continue seeing | ||||||
|  |         // a backing even if they're no longer allowed to see the initiative. | ||||||
|  |  | ||||||
|  |         $initiative = $this->getInitiative(); | ||||||
|  |         if ($initiative) { | ||||||
|  |           return $initiative->getPolicy($capability); | ||||||
|  |         } | ||||||
|  |         return PhabricatorPolicies::POLICY_NOONE; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { | ||||||
|  |     return ($viewer->getPHID() == $this->getBackerPHID()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function describeAutomaticCapability($capability) { | ||||||
|  |     return pht('A backer can always see what they have backed.'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   public function getApplicationTransactionEditor() { | ||||||
|  |     return new FundBackingEditor(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getApplicationTransactionObject() { | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getApplicationTransactionTemplate() { | ||||||
|  |     return new FundBackingTransaction(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								src/applications/fund/storage/FundBackingTransaction.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/applications/fund/storage/FundBackingTransaction.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundBackingTransaction | ||||||
|  |   extends PhabricatorApplicationTransaction { | ||||||
|  |  | ||||||
|  |   public function getApplicationName() { | ||||||
|  |     return 'fund'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getApplicationTransactionType() { | ||||||
|  |     return FundBackingPHIDType::TYPECONST; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getApplicationTransactionCommentObject() { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								src/applications/fund/storage/FundDAO.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/applications/fund/storage/FundDAO.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | abstract class FundDAO extends PhabricatorLiskDAO { | ||||||
|  |  | ||||||
|  |   public function getApplicationName() { | ||||||
|  |     return 'fund'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										157
									
								
								src/applications/fund/storage/FundInitiative.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								src/applications/fund/storage/FundInitiative.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,157 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundInitiative extends FundDAO | ||||||
|  |   implements | ||||||
|  |     PhabricatorPolicyInterface, | ||||||
|  |     PhabricatorProjectInterface, | ||||||
|  |     PhabricatorApplicationTransactionInterface, | ||||||
|  |     PhabricatorSubscribableInterface, | ||||||
|  |     PhabricatorMentionableInterface, | ||||||
|  |     PhabricatorFlaggableInterface, | ||||||
|  |     PhabricatorTokenReceiverInterface, | ||||||
|  |     PhabricatorDestructibleInterface { | ||||||
|  |  | ||||||
|  |   protected $name; | ||||||
|  |   protected $ownerPHID; | ||||||
|  |   protected $description; | ||||||
|  |   protected $viewPolicy; | ||||||
|  |   protected $editPolicy; | ||||||
|  |   protected $status; | ||||||
|  |  | ||||||
|  |   private $projectPHIDs = self::ATTACHABLE; | ||||||
|  |  | ||||||
|  |   const STATUS_OPEN = 'open'; | ||||||
|  |   const STATUS_CLOSED = 'closed'; | ||||||
|  |  | ||||||
|  |   public static function getStatusNameMap() { | ||||||
|  |     return array( | ||||||
|  |       self::STATUS_OPEN => pht('Open'), | ||||||
|  |       self::STATUS_CLOSED => pht('Closed'), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public static function initializeNewInitiative(PhabricatorUser $actor) { | ||||||
|  |     $app = id(new PhabricatorApplicationQuery()) | ||||||
|  |       ->setViewer($actor) | ||||||
|  |       ->withClasses(array('PhabricatorFundApplication')) | ||||||
|  |       ->executeOne(); | ||||||
|  |  | ||||||
|  |     $view_policy = $app->getPolicy(FundDefaultViewCapability::CAPABILITY); | ||||||
|  |  | ||||||
|  |     return id(new FundInitiative()) | ||||||
|  |       ->setOwnerPHID($actor->getPHID()) | ||||||
|  |       ->setViewPolicy($view_policy) | ||||||
|  |       ->setEditPolicy($actor->getPHID()) | ||||||
|  |       ->setStatus(self::STATUS_OPEN); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getConfiguration() { | ||||||
|  |     return array( | ||||||
|  |       self::CONFIG_AUX_PHID => true, | ||||||
|  |     ) + parent::getConfiguration(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function generatePHID() { | ||||||
|  |     return PhabricatorPHID::generateNewPHID(FundInitiativePHIDType::TYPECONST); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getMonogram() { | ||||||
|  |     return 'I'.$this->getID(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getProjectPHIDs() { | ||||||
|  |     return $this->assertAttached($this->projectPHIDs); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function attachProjectPHIDs(array $phids) { | ||||||
|  |     $this->projectPHIDs = $phids; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function isClosed() { | ||||||
|  |     return ($this->getStatus() == self::STATUS_CLOSED); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /* -(  PhabricatorPolicyInterface  )----------------------------------------- */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   public function getCapabilities() { | ||||||
|  |     return array( | ||||||
|  |       PhabricatorPolicyCapability::CAN_VIEW, | ||||||
|  |       PhabricatorPolicyCapability::CAN_EDIT, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getPolicy($capability) { | ||||||
|  |     switch ($capability) { | ||||||
|  |       case PhabricatorPolicyCapability::CAN_VIEW: | ||||||
|  |         return $this->getViewPolicy(); | ||||||
|  |       case PhabricatorPolicyCapability::CAN_EDIT: | ||||||
|  |         return $this->getEditPolicy(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function describeAutomaticCapability($capability) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   public function getApplicationTransactionEditor() { | ||||||
|  |     return new FundInitiativeEditor(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getApplicationTransactionObject() { | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getApplicationTransactionTemplate() { | ||||||
|  |     return new FundInitiativeTransaction(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /* -(  PhabricatorSubscribableInterface  )----------------------------------- */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   public function isAutomaticallySubscribed($phid) { | ||||||
|  |     return ($phid == $this->getOwnerPHID()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function shouldShowSubscribersProperty() { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function shouldAllowSubscription($phid) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /* -(  PhabricatorTokenRecevierInterface  )---------------------------------- */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   public function getUsersToNotifyOfTokenGiven() { | ||||||
|  |     return array( | ||||||
|  |       $this->getOwnerPHID(), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /* -(  PhabricatorDestructibleInterface  )----------------------------------- */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   public function destroyObjectPermanently( | ||||||
|  |     PhabricatorDestructionEngine $engine) { | ||||||
|  |  | ||||||
|  |     $this->openTransaction(); | ||||||
|  |       $this->delete(); | ||||||
|  |     $this->saveTransaction(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										136
									
								
								src/applications/fund/storage/FundInitiativeTransaction.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								src/applications/fund/storage/FundInitiativeTransaction.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class FundInitiativeTransaction | ||||||
|  |   extends PhabricatorApplicationTransaction { | ||||||
|  |  | ||||||
|  |   const TYPE_NAME = 'fund:name'; | ||||||
|  |   const TYPE_DESCRIPTION = 'fund:description'; | ||||||
|  |   const TYPE_STATUS = 'fund:status'; | ||||||
|  |  | ||||||
|  |   public function getApplicationName() { | ||||||
|  |     return 'fund'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getApplicationTransactionType() { | ||||||
|  |     return FundInitiativePHIDType::TYPECONST; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getApplicationTransactionCommentObject() { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getTitle() { | ||||||
|  |     $author_phid = $this->getAuthorPHID(); | ||||||
|  |     $object_phid = $this->getObjectPHID(); | ||||||
|  |  | ||||||
|  |     $old = $this->getOldValue(); | ||||||
|  |     $new = $this->getNewValue(); | ||||||
|  |  | ||||||
|  |     $type = $this->getTransactionType(); | ||||||
|  |     switch ($type) { | ||||||
|  |       case FundInitiativeTransaction::TYPE_NAME: | ||||||
|  |         if ($old === null) { | ||||||
|  |           return pht( | ||||||
|  |             '%s created this initiative.', | ||||||
|  |             $this->renderHandleLink($author_phid)); | ||||||
|  |         } else { | ||||||
|  |           return pht( | ||||||
|  |             '%s renamed this initiative from "%s" to "%s".', | ||||||
|  |             $this->renderHandleLink($author_phid), | ||||||
|  |             $old, | ||||||
|  |             $new); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |       case FundInitiativeTransaction::TYPE_DESCRIPTION: | ||||||
|  |         return pht( | ||||||
|  |           '%s edited the description of this initiative.', | ||||||
|  |           $this->renderHandleLink($author_phid)); | ||||||
|  |       case FundInitiativeTransaction::TYPE_STATUS: | ||||||
|  |         switch ($new) { | ||||||
|  |           case FundInitiative::STATUS_OPEN: | ||||||
|  |             return pht( | ||||||
|  |               '%s reopened this initiative.', | ||||||
|  |               $this->renderHandleLink($author_phid)); | ||||||
|  |           case FundInitiative::STATUS_CLOSED: | ||||||
|  |             return pht( | ||||||
|  |               '%s closed this initiative.', | ||||||
|  |               $this->renderHandleLink($author_phid)); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return parent::getTitle(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getTitleForFeed(PhabricatorFeedStory $story) { | ||||||
|  |     $author_phid = $this->getAuthorPHID(); | ||||||
|  |     $object_phid = $this->getObjectPHID(); | ||||||
|  |  | ||||||
|  |     $old = $this->getOldValue(); | ||||||
|  |     $new = $this->getNewValue(); | ||||||
|  |  | ||||||
|  |     $type = $this->getTransactionType(); | ||||||
|  |     switch ($type) { | ||||||
|  |       case FundInitiativeTransaction::TYPE_NAME: | ||||||
|  |         if ($old === null) { | ||||||
|  |           return pht( | ||||||
|  |             '%s created %s.', | ||||||
|  |             $this->renderHandleLink($author_phid), | ||||||
|  |             $this->renderHandleLink($object_phid)); | ||||||
|  |  | ||||||
|  |         } else { | ||||||
|  |           return pht( | ||||||
|  |             '%s renamed %s.', | ||||||
|  |             $this->renderHandleLink($author_phid), | ||||||
|  |             $this->renderHandleLink($object_phid)); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |       case FundInitiativeTransaction::TYPE_DESCRIPTION: | ||||||
|  |         return pht( | ||||||
|  |           '%s updated the description for %s.', | ||||||
|  |           $this->renderHandleLink($author_phid), | ||||||
|  |           $this->renderHandleLink($object_phid)); | ||||||
|  |       case FundInitiativeTransaction::TYPE_STATUS: | ||||||
|  |         switch ($new) { | ||||||
|  |           case FundInitiative::STATUS_OPEN: | ||||||
|  |             return pht( | ||||||
|  |               '%s reopened %s.', | ||||||
|  |               $this->renderHandleLink($author_phid), | ||||||
|  |               $this->renderHandleLink($object_phid)); | ||||||
|  |           case FundInitiative::STATUS_CLOSED: | ||||||
|  |             return pht( | ||||||
|  |               '%s closed %s.', | ||||||
|  |               $this->renderHandleLink($author_phid), | ||||||
|  |               $this->renderHandleLink($object_phid)); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return parent::getTitleForFeed($story); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function shouldHide() { | ||||||
|  |     $old = $this->getOldValue(); | ||||||
|  |     switch ($this->getTransactionType()) { | ||||||
|  |       case FundInitiativeTransaction::TYPE_DESCRIPTION: | ||||||
|  |         return ($old === null); | ||||||
|  |     } | ||||||
|  |     return parent::shouldHide(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function hasChangeDetails() { | ||||||
|  |     switch ($this->getTransactionType()) { | ||||||
|  |       case FundInitiativeTransaction::TYPE_DESCRIPTION: | ||||||
|  |         return ($this->getOldValue() !== null); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return parent::hasChangeDetails(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function renderChangeDetails(PhabricatorUser $viewer) { | ||||||
|  |     return $this->renderTextCorpusChangeDetails( | ||||||
|  |       $viewer, | ||||||
|  |       $this->getOldValue(), | ||||||
|  |       $this->getNewValue()); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -119,6 +119,7 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList { | |||||||
|       'db.phragment' => array(), |       'db.phragment' => array(), | ||||||
|       'db.dashboard' => array(), |       'db.dashboard' => array(), | ||||||
|       'db.system' => array(), |       'db.system' => array(), | ||||||
|  |       'db.fund' => array(), | ||||||
|       '0000.legacy.sql' => array( |       '0000.legacy.sql' => array( | ||||||
|         'legacy' => 0, |         'legacy' => 0, | ||||||
|       ), |       ), | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 epriestley
					epriestley