@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

Add boilerplate scaffolding for Phortune subscriptions

Summary:
Ref T6881. This roughs in the major objects, support classes, and controllers.

- Show subscriptions on account detail.
- Browse all account subscriptions.
- Link to active subsciptions from merchant detail.

Test Plan: Clicked around in the UI. There's no way to create subscriptions yet, so I basically just kicked the tires on this. I probably missed a few things that I'll clean up in followups.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T6881

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

+744 -1
+20
resources/sql/autopatches/20150124.subs.1.sql
··· 1 + CREATE TABLE {$NAMESPACE}_phortune.phortune_subscription ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARBINARY(64) NOT NULL, 4 + accountPHID VARBINARY(64) NOT NULL, 5 + merchantPHID VARBINARY(64) NOT NULL, 6 + triggerPHID VARBINARY(64) NOT NULL, 7 + authorPHID VARBINARY(64) NOT NULL, 8 + subscriptionClassKey BINARY(12) NOT NULL, 9 + subscriptionClass VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT}, 10 + subscriptionRefKey BINARY(12) NOT NULL, 11 + subscriptionRef VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT}, 12 + status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, 13 + metadata LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, 14 + dateCreated INT UNSIGNED NOT NULL, 15 + dateModified INT UNSIGNED NOT NULL, 16 + UNIQUE KEY `key_phid` (phid), 17 + UNIQUE KEY `key_subscription` (subscriptionClassKey, subscriptionRefKey), 18 + KEY `key_account` (accountPHID), 19 + KEY `key_merchant` (merchantPHID) 20 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+16
src/__phutil_library_map__.php
··· 2804 2804 'PhortunePurchaseQuery' => 'applications/phortune/query/PhortunePurchaseQuery.php', 2805 2805 'PhortuneSchemaSpec' => 'applications/phortune/storage/PhortuneSchemaSpec.php', 2806 2806 'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php', 2807 + 'PhortuneSubscription' => 'applications/phortune/storage/PhortuneSubscription.php', 2808 + 'PhortuneSubscriptionImplementation' => 'applications/phortune/subscription/PhortuneSubscriptionImplementation.php', 2809 + 'PhortuneSubscriptionListController' => 'applications/phortune/controller/PhortuneSubscriptionListController.php', 2810 + 'PhortuneSubscriptionPHIDType' => 'applications/phortune/phid/PhortuneSubscriptionPHIDType.php', 2811 + 'PhortuneSubscriptionQuery' => 'applications/phortune/query/PhortuneSubscriptionQuery.php', 2812 + 'PhortuneSubscriptionSearchEngine' => 'applications/phortune/query/PhortuneSubscriptionSearchEngine.php', 2813 + 'PhortuneSubscriptionTableView' => 'applications/phortune/view/PhortuneSubscriptionTableView.php', 2807 2814 'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php', 2808 2815 'PhortuneWePayPaymentProvider' => 'applications/phortune/provider/PhortuneWePayPaymentProvider.php', 2809 2816 'PhragmentBrowseController' => 'applications/phragment/controller/PhragmentBrowseController.php', ··· 6144 6151 'PhortunePurchaseQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 6145 6152 'PhortuneSchemaSpec' => 'PhabricatorConfigSchemaSpec', 6146 6153 'PhortuneStripePaymentProvider' => 'PhortunePaymentProvider', 6154 + 'PhortuneSubscription' => array( 6155 + 'PhortuneDAO', 6156 + 'PhabricatorPolicyInterface', 6157 + ), 6158 + 'PhortuneSubscriptionListController' => 'PhortuneController', 6159 + 'PhortuneSubscriptionPHIDType' => 'PhabricatorPHIDType', 6160 + 'PhortuneSubscriptionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 6161 + 'PhortuneSubscriptionSearchEngine' => 'PhabricatorApplicationSearchEngine', 6162 + 'PhortuneSubscriptionTableView' => 'AphrontView', 6147 6163 'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider', 6148 6164 'PhortuneWePayPaymentProvider' => 'PhortunePaymentProvider', 6149 6165 'PhragmentBrowseController' => 'PhragmentController',
+5 -1
src/applications/phortune/application/PhabricatorPhortuneApplication.php
··· 45 45 ), 46 46 'order/(?:query/(?P<queryKey>[^/]+)/)?' 47 47 => 'PhortuneCartListController', 48 + 'subscription/(?:query/(?P<queryKey>[^/]+)/)?' 49 + => 'PhortuneSubscriptionListController', 48 50 'charge/(?:query/(?P<queryKey>[^/]+)/)?' 49 51 => 'PhortuneChargeListController', 50 52 ), ··· 77 79 'merchant/' => array( 78 80 '(?:query/(?P<queryKey>[^/]+)/)?' => 'PhortuneMerchantListController', 79 81 'edit/(?:(?P<id>\d+)/)?' => 'PhortuneMerchantEditController', 80 - 'orders/(?P<merchantID>\d+)/(?:query/(?P<querKey>[^/]+)/)?' 82 + 'orders/(?P<merchantID>\d+)/(?:query/(?P<queryKey>[^/]+)/)?' 81 83 => 'PhortuneCartListController', 84 + 'subscription/(?P<merchantID>\d+)/(?:query/(?P<queryKey>[^/]+)/)?' 85 + => 'PhortuneSubscriptionListController', 82 86 '(?P<id>\d+)/' => 'PhortuneMerchantViewController', 83 87 ), 84 88 ),
+36
src/applications/phortune/controller/PhortuneAccountViewController.php
··· 70 70 $payment_methods = $this->buildPaymentMethodsSection($account); 71 71 $purchase_history = $this->buildPurchaseHistorySection($account); 72 72 $charge_history = $this->buildChargeHistorySection($account); 73 + $subscriptions = $this->buildSubscriptionsSection($account); 74 + 73 75 $timeline = $this->buildTransactionTimeline( 74 76 $account, 75 77 new PhortuneAccountTransactionQuery()); ··· 86 88 $payment_methods, 87 89 $purchase_history, 88 90 $charge_history, 91 + $subscriptions, 89 92 $timeline, 90 93 ), 91 94 array( ··· 253 256 ->setIconFont('fa-list')) 254 257 ->setHref($charges_uri) 255 258 ->setText(pht('View All Charges'))); 259 + 260 + return id(new PHUIObjectBoxView()) 261 + ->setHeader($header) 262 + ->appendChild($table); 263 + } 264 + 265 + private function buildSubscriptionsSection(PhortuneAccount $account) { 266 + $request = $this->getRequest(); 267 + $viewer = $request->getUser(); 268 + 269 + $subscriptions = id(new PhortuneSubscriptionQuery()) 270 + ->setViewer($viewer) 271 + ->withAccountPHIDs(array($account->getPHID())) 272 + ->setLimit(10) 273 + ->execute(); 274 + 275 + $subscriptions_uri = $this->getApplicationURI( 276 + $account->getID().'/subscription/'); 277 + 278 + $table = id(new PhortuneSubscriptionTableView()) 279 + ->setUser($viewer) 280 + ->setSubscriptions($subscriptions); 281 + 282 + $header = id(new PHUIHeaderView()) 283 + ->setHeader(pht('Recent Subscriptions')) 284 + ->addActionLink( 285 + id(new PHUIButtonView()) 286 + ->setTag('a') 287 + ->setIcon( 288 + id(new PHUIIconView()) 289 + ->setIconFont('fa-list')) 290 + ->setHref($subscriptions_uri) 291 + ->setText(pht('View All Subscriptions'))); 256 292 257 293 return id(new PHUIObjectBoxView()) 258 294 ->setHeader($header)
+8
src/applications/phortune/controller/PhortuneMerchantViewController.php
··· 186 186 ->setDisabled(!$can_edit) 187 187 ->setWorkflow(!$can_edit)); 188 188 189 + $view->addAction( 190 + id(new PhabricatorActionView()) 191 + ->setName(pht('View Subscriptions')) 192 + ->setIcon('fa-moon-o') 193 + ->setHref($this->getApplicationURI("merchant/subscription/{$id}/")) 194 + ->setDisabled(!$can_edit) 195 + ->setWorkflow(!$can_edit)); 196 + 189 197 return $view; 190 198 } 191 199
+110
src/applications/phortune/controller/PhortuneSubscriptionListController.php
··· 1 + <?php 2 + 3 + final class PhortuneSubscriptionListController 4 + extends PhortuneController { 5 + 6 + private $accountID; 7 + private $merchantID; 8 + private $queryKey; 9 + 10 + private $merchant; 11 + private $account; 12 + 13 + public function willProcessRequest(array $data) { 14 + $this->merchantID = idx($data, 'merchantID'); 15 + $this->accountID = idx($data, 'accountID'); 16 + $this->queryKey = idx($data, 'queryKey'); 17 + } 18 + 19 + public function processRequest() { 20 + $request = $this->getRequest(); 21 + $viewer = $request->getUser(); 22 + 23 + $engine = new PhortuneSubscriptionSearchEngine(); 24 + 25 + if ($this->merchantID) { 26 + $merchant = id(new PhortuneMerchantQuery()) 27 + ->setViewer($viewer) 28 + ->withIDs(array($this->merchantID)) 29 + ->requireCapabilities( 30 + array( 31 + PhabricatorPolicyCapability::CAN_VIEW, 32 + PhabricatorPolicyCapability::CAN_EDIT, 33 + )) 34 + ->executeOne(); 35 + if (!$merchant) { 36 + return new Aphront404Response(); 37 + } 38 + $this->merchant = $merchant; 39 + $engine->setMerchant($merchant); 40 + } else if ($this->accountID) { 41 + $account = id(new PhortuneAccountQuery()) 42 + ->setViewer($viewer) 43 + ->withIDs(array($this->accountID)) 44 + ->requireCapabilities( 45 + array( 46 + PhabricatorPolicyCapability::CAN_VIEW, 47 + PhabricatorPolicyCapability::CAN_EDIT, 48 + )) 49 + ->executeOne(); 50 + if (!$account) { 51 + return new Aphront404Response(); 52 + } 53 + $this->account = $account; 54 + $engine->setAccount($account); 55 + } else { 56 + return new Aphront404Response(); 57 + } 58 + 59 + $controller = id(new PhabricatorApplicationSearchController()) 60 + ->setQueryKey($this->queryKey) 61 + ->setSearchEngine($engine) 62 + ->setNavigation($this->buildSideNavView()); 63 + 64 + return $this->delegateToController($controller); 65 + } 66 + 67 + public function buildSideNavView() { 68 + $viewer = $this->getRequest()->getUser(); 69 + 70 + $nav = new AphrontSideNavFilterView(); 71 + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 72 + 73 + id(new PhortuneSubscriptionSearchEngine()) 74 + ->setViewer($viewer) 75 + ->addNavigationItems($nav->getMenu()); 76 + 77 + $nav->selectFilter(null); 78 + 79 + return $nav; 80 + } 81 + 82 + protected function buildApplicationCrumbs() { 83 + $crumbs = parent::buildApplicationCrumbs(); 84 + 85 + $merchant = $this->merchant; 86 + if ($merchant) { 87 + $id = $merchant->getID(); 88 + $crumbs->addTextCrumb( 89 + $merchant->getName(), 90 + $this->getApplicationURI("merchant/{$id}/")); 91 + $crumbs->addTextCrumb( 92 + pht('Subscriptions'), 93 + $this->getApplicationURI("merchant/subscriptions/{$id}/")); 94 + } 95 + 96 + $account = $this->account; 97 + if ($account) { 98 + $id = $account->getID(); 99 + $crumbs->addTextCrumb( 100 + $account->getName(), 101 + $this->getApplicationURI("{$id}/")); 102 + $crumbs->addTextCrumb( 103 + pht('Subscriptions'), 104 + $this->getApplicationURI("{$id}/subscription/")); 105 + } 106 + 107 + return $crumbs; 108 + } 109 + 110 + }
+38
src/applications/phortune/phid/PhortuneSubscriptionPHIDType.php
··· 1 + <?php 2 + 3 + final class PhortuneSubscriptionPHIDType extends PhabricatorPHIDType { 4 + 5 + const TYPECONST = 'PSUB'; 6 + 7 + public function getTypeName() { 8 + return pht('Phortune Subscription'); 9 + } 10 + 11 + public function newObject() { 12 + return new PhortuneSubscription(); 13 + } 14 + 15 + protected function buildQueryForObjects( 16 + PhabricatorObjectQuery $query, 17 + array $phids) { 18 + 19 + return id(new PhortuneSubscriptionQuery()) 20 + ->withPHIDs($phids); 21 + } 22 + 23 + public function loadHandles( 24 + PhabricatorHandleQuery $query, 25 + array $handles, 26 + array $objects) { 27 + 28 + foreach ($handles as $phid => $handle) { 29 + $subscription = $objects[$phid]; 30 + 31 + $id = $subscription->getID(); 32 + 33 + // TODO: Flesh this out. 34 + 35 + } 36 + } 37 + 38 + }
+152
src/applications/phortune/query/PhortuneSubscriptionQuery.php
··· 1 + <?php 2 + 3 + final class PhortuneSubscriptionQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $phids; 8 + private $accountPHIDs; 9 + private $merchantPHIDs; 10 + private $statuses; 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 withAccountPHIDs(array $account_phids) { 23 + $this->accountPHIDs = $account_phids; 24 + return $this; 25 + } 26 + 27 + public function withMerchantPHIDs(array $merchant_phids) { 28 + $this->merchantPHIDs = $merchant_phids; 29 + return $this; 30 + } 31 + 32 + public function withStatuses(array $statuses) { 33 + $this->statuses = $statuses; 34 + return $this; 35 + } 36 + 37 + protected function loadPage() { 38 + $table = new PhortuneSubscription(); 39 + $conn = $table->establishConnection('r'); 40 + 41 + $rows = queryfx_all( 42 + $conn, 43 + 'SELECT subscription.* FROM %T subscription %Q %Q %Q', 44 + $table->getTableName(), 45 + $this->buildWhereClause($conn), 46 + $this->buildOrderClause($conn), 47 + $this->buildLimitClause($conn)); 48 + 49 + return $table->loadAllFromArray($rows); 50 + } 51 + 52 + protected function willFilterPage(array $subscriptions) { 53 + $accounts = id(new PhortuneAccountQuery()) 54 + ->setViewer($this->getViewer()) 55 + ->withPHIDs(mpull($subscriptions, 'getAccountPHID')) 56 + ->execute(); 57 + $accounts = mpull($accounts, null, 'getPHID'); 58 + 59 + foreach ($subscriptions as $key => $subscription) { 60 + $account = idx($accounts, $subscription->getAccountPHID()); 61 + if (!$account) { 62 + unset($subscriptions[$key]); 63 + continue; 64 + } 65 + $subscription->attachAccount($account); 66 + } 67 + 68 + $merchants = id(new PhortuneMerchantQuery()) 69 + ->setViewer($this->getViewer()) 70 + ->withPHIDs(mpull($subscriptions, 'getMerchantPHID')) 71 + ->execute(); 72 + $merchants = mpull($merchants, null, 'getPHID'); 73 + 74 + foreach ($subscriptions as $key => $subscription) { 75 + $merchant = idx($merchants, $subscription->getMerchantPHID()); 76 + if (!$merchant) { 77 + unset($subscriptions[$key]); 78 + continue; 79 + } 80 + $subscription->attachMerchant($merchant); 81 + } 82 + 83 + $implementations = array(); 84 + 85 + $subscription_map = mgroup($subscriptions, 'getSubscriptionClass'); 86 + foreach ($subscription_map as $class => $class_subscriptions) { 87 + $sub = newv($class, array()); 88 + $implementations += $sub->loadImplementationsForSubscriptions( 89 + $this->getViewer(), 90 + $class_subscriptions); 91 + } 92 + 93 + foreach ($subscriptions as $key => $subscription) { 94 + $implementation = idx($implementations, $key); 95 + if (!$implementation) { 96 + unset($subscriptions[$key]); 97 + continue; 98 + } 99 + $subscription->attachImplementation($implementation); 100 + } 101 + 102 + return $subscriptions; 103 + } 104 + 105 + private function buildWhereClause(AphrontDatabaseConnection $conn) { 106 + $where = array(); 107 + 108 + $where[] = $this->buildPagingClause($conn); 109 + 110 + if ($this->ids !== null) { 111 + $where[] = qsprintf( 112 + $conn, 113 + 'subscription.id IN (%Ld)', 114 + $this->ids); 115 + } 116 + 117 + if ($this->phids !== null) { 118 + $where[] = qsprintf( 119 + $conn, 120 + 'subscription.phid IN (%Ls)', 121 + $this->phids); 122 + } 123 + 124 + if ($this->accountPHIDs !== null) { 125 + $where[] = qsprintf( 126 + $conn, 127 + 'subscription.accountPHID IN (%Ls)', 128 + $this->accountPHIDs); 129 + } 130 + 131 + if ($this->merchantPHIDs !== null) { 132 + $where[] = qsprintf( 133 + $conn, 134 + 'subscription.merchantPHID IN (%Ls)', 135 + $this->merchantPHIDs); 136 + } 137 + 138 + if ($this->statuses !== null) { 139 + $where[] = qsprintf( 140 + $conn, 141 + 'subscription.status IN (%Ls)', 142 + $this->statuses); 143 + } 144 + 145 + return $this->formatWhereClause($where); 146 + } 147 + 148 + public function getQueryApplicationClass() { 149 + return 'PhabricatorPhortuneApplication'; 150 + } 151 + 152 + }
+154
src/applications/phortune/query/PhortuneSubscriptionSearchEngine.php
··· 1 + <?php 2 + 3 + final class PhortuneSubscriptionSearchEngine 4 + extends PhabricatorApplicationSearchEngine { 5 + 6 + private $merchant; 7 + private $account; 8 + 9 + public function setAccount(PhortuneAccount $account) { 10 + $this->account = $account; 11 + return $this; 12 + } 13 + 14 + public function getAccount() { 15 + return $this->account; 16 + } 17 + 18 + public function setMerchant(PhortuneMerchant $merchant) { 19 + $this->merchant = $merchant; 20 + return $this; 21 + } 22 + 23 + public function getMerchant() { 24 + return $this->merchant; 25 + } 26 + 27 + public function getResultTypeDescription() { 28 + return pht('Phortune Subscriptions'); 29 + } 30 + 31 + public function buildSavedQueryFromRequest(AphrontRequest $request) { 32 + $saved = new PhabricatorSavedQuery(); 33 + 34 + return $saved; 35 + } 36 + 37 + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { 38 + $query = id(new PhortuneSubscriptionQuery()); 39 + 40 + $viewer = $this->requireViewer(); 41 + 42 + $merchant = $this->getMerchant(); 43 + $account = $this->getAccount(); 44 + if ($merchant) { 45 + $can_edit = PhabricatorPolicyFilter::hasCapability( 46 + $viewer, 47 + $merchant, 48 + PhabricatorPolicyCapability::CAN_EDIT); 49 + if (!$can_edit) { 50 + throw new Exception( 51 + pht( 52 + 'You can not query subscriptions for a merchant you do not '. 53 + 'control.')); 54 + } 55 + $query->withMerchantPHIDs(array($merchant->getPHID())); 56 + } else if ($account) { 57 + $can_edit = PhabricatorPolicyFilter::hasCapability( 58 + $viewer, 59 + $account, 60 + PhabricatorPolicyCapability::CAN_EDIT); 61 + if (!$can_edit) { 62 + throw new Exception( 63 + pht( 64 + 'You can not query subscriptions for an account you are not '. 65 + 'a member of.')); 66 + } 67 + $query->withAccountPHIDs(array($account->getPHID())); 68 + } else { 69 + $accounts = id(new PhortuneAccountQuery()) 70 + ->withMemberPHIDs(array($viewer->getPHID())) 71 + ->execute(); 72 + if ($accounts) { 73 + $query->withAccountPHIDs(mpull($accounts, 'getPHID')); 74 + } else { 75 + throw new Exception(pht('You have no accounts!')); 76 + } 77 + } 78 + 79 + return $query; 80 + } 81 + 82 + public function buildSearchForm( 83 + AphrontFormView $form, 84 + PhabricatorSavedQuery $saved_query) {} 85 + 86 + protected function getURI($path) { 87 + $merchant = $this->getMerchant(); 88 + $account = $this->getAccount(); 89 + if ($merchant) { 90 + return '/phortune/merchant/'.$merchant->getID().'/subscription/'.$path; 91 + } else if ($account) { 92 + return '/phortune/'.$account->getID().'/subscription/'; 93 + } else { 94 + return '/phortune/subscription/'.$path; 95 + } 96 + } 97 + 98 + protected function getBuiltinQueryNames() { 99 + $names = array( 100 + 'all' => pht('All Subscriptions'), 101 + ); 102 + 103 + return $names; 104 + } 105 + 106 + public function buildSavedQueryFromBuiltin($query_key) { 107 + 108 + $query = $this->newSavedQuery(); 109 + $query->setQueryKey($query_key); 110 + 111 + switch ($query_key) { 112 + case 'all': 113 + return $query; 114 + } 115 + 116 + return parent::buildSavedQueryFromBuiltin($query_key); 117 + } 118 + 119 + protected function getRequiredHandlePHIDsForResultList( 120 + array $subscriptions, 121 + PhabricatorSavedQuery $query) { 122 + $phids = array(); 123 + foreach ($subscriptions as $subscription) { 124 + $phids[] = $subscription->getPHID(); 125 + $phids[] = $subscription->getMerchantPHID(); 126 + $phids[] = $subscription->getAuthorPHID(); 127 + } 128 + return $phids; 129 + } 130 + 131 + protected function renderResultList( 132 + array $subscriptions, 133 + PhabricatorSavedQuery $query, 134 + array $handles) { 135 + assert_instances_of($subscriptions, 'PhortuneSubscription'); 136 + 137 + $viewer = $this->requireViewer(); 138 + 139 + $table = id(new PhortuneSubscriptionTableView()) 140 + ->setUser($viewer) 141 + ->setSubscriptions($subscriptions); 142 + 143 + $merchant = $this->getMerchant(); 144 + if ($merchant) { 145 + $header = pht('Subscriptions for %s', $merchant->getName()); 146 + } else { 147 + $header = pht('Your Subscriptions'); 148 + } 149 + 150 + return id(new PHUIObjectBoxView()) 151 + ->setHeaderText($header) 152 + ->appendChild($table); 153 + } 154 + }
+131
src/applications/phortune/storage/PhortuneSubscription.php
··· 1 + <?php 2 + 3 + /** 4 + * A subscription bills users regularly. 5 + */ 6 + final class PhortuneSubscription extends PhortuneDAO 7 + implements PhabricatorPolicyInterface { 8 + 9 + const STATUS_ACTIVE = 'active'; 10 + const STATUS_CANCELLED = 'cancelled'; 11 + 12 + protected $accountPHID; 13 + protected $merchantPHID; 14 + protected $triggerPHID; 15 + protected $authorPHID; 16 + protected $subscriptionClassKey; 17 + protected $subscriptionClass; 18 + protected $subscriptionRefKey; 19 + protected $subscriptionRef; 20 + protected $status; 21 + protected $metadata = array(); 22 + 23 + private $merchant = self::ATTACHABLE; 24 + private $account = self::ATTACHABLE; 25 + private $implementation = self::ATTACHABLE; 26 + private $trigger = self::ATTACHABLE; 27 + 28 + protected function getConfiguration() { 29 + return array( 30 + self::CONFIG_AUX_PHID => true, 31 + self::CONFIG_SERIALIZATION => array( 32 + 'metadata' => self::SERIALIZATION_JSON, 33 + ), 34 + self::CONFIG_COLUMN_SCHEMA => array( 35 + 'subscriptionClassKey' => 'bytes12', 36 + 'subscriptionClass' => 'text128', 37 + 'subscriptionRefKey' => 'bytes12', 38 + 'subscriptionRef' => 'text128', 39 + 'status' => 'text32', 40 + ), 41 + self::CONFIG_KEY_SCHEMA => array( 42 + 'key_subscription' => array( 43 + 'columns' => array('subscriptionClassKey', 'subscriptionRefKey'), 44 + 'unique' => true, 45 + ), 46 + 'key_account' => array( 47 + 'columns' => array('accountPHID'), 48 + ), 49 + 'key_merchant' => array( 50 + 'columns' => array('merchantPHID'), 51 + ), 52 + ), 53 + ) + parent::getConfiguration(); 54 + } 55 + 56 + public function generatePHID() { 57 + return PhabricatorPHID::generateNewPHID( 58 + PhortuneSubscriptionPHIDType::TYPECONST); 59 + } 60 + 61 + public static function initializeNewSubscription() { 62 + return id(new PhortuneSubscription()); 63 + } 64 + 65 + public function attachImplementation( 66 + PhortuneSubscriptionImplementation $impl) { 67 + $this->implementation = $impl; 68 + } 69 + 70 + public function getImplementation() { 71 + return $this->assertAttached($this->implementation); 72 + } 73 + 74 + public function save() { 75 + $this->subscriptionClassKey = PhabricatorHash::digestForIndex( 76 + $this->subscriptionClass); 77 + 78 + $this->subscriptionRefKey = PhabricatorHash::digestForIndex( 79 + $this->subscriptionRef); 80 + 81 + return parent::save(); 82 + } 83 + 84 + 85 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 86 + 87 + 88 + public function getCapabilities() { 89 + return array( 90 + PhabricatorPolicyCapability::CAN_VIEW, 91 + PhabricatorPolicyCapability::CAN_EDIT, 92 + ); 93 + } 94 + 95 + public function getPolicy($capability) { 96 + // NOTE: Both view and edit use the account's edit policy. We punch a hole 97 + // through this for merchants, below. 98 + return $this 99 + ->getAccount() 100 + ->getPolicy(PhabricatorPolicyCapability::CAN_EDIT); 101 + } 102 + 103 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 104 + if ($this->getAccount()->hasAutomaticCapability($capability, $viewer)) { 105 + return true; 106 + } 107 + 108 + // If the viewer controls the merchant this subscription bills to, they can 109 + // view the subscription. 110 + if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { 111 + $can_admin = PhabricatorPolicyFilter::hasCapability( 112 + $viewer, 113 + $this->getMerchant(), 114 + PhabricatorPolicyCapability::CAN_EDIT); 115 + if ($can_admin) { 116 + return true; 117 + } 118 + } 119 + 120 + return false; 121 + } 122 + 123 + public function describeAutomaticCapability($capability) { 124 + return array( 125 + pht('Subscriptions inherit the policies of the associated account.'), 126 + pht( 127 + 'The merchant you are subscribed with can review and manage the '. 128 + 'subscription.'), 129 + ); 130 + } 131 + }
+18
src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php
··· 1 + <?php 2 + 3 + abstract class PhortuneSubscriptionImplementation { 4 + 5 + abstract public function loadImplementationsForRefs( 6 + PhabricatorUser $viewer, 7 + array $refs); 8 + 9 + abstract public function getRef(); 10 + abstract public function getName(PhortuneSubscription $subscription); 11 + 12 + protected function getContentSource() { 13 + return PhabricatorContentSource::newForSource( 14 + PhabricatorContentSource::SOURCE_PHORTUNE, 15 + array()); 16 + } 17 + 18 + }
+56
src/applications/phortune/view/PhortuneSubscriptionTableView.php
··· 1 + <?php 2 + 3 + final class PhortuneSubscriptionTableView extends AphrontView { 4 + 5 + private $subscriptions; 6 + private $handles; 7 + 8 + public function setHandles(array $handles) { 9 + $this->handles = $handles; 10 + return $this; 11 + } 12 + 13 + public function getHandles() { 14 + return $this->handles; 15 + } 16 + 17 + public function setSubscriptions(array $subscriptions) { 18 + $this->subscriptions = $subscriptions; 19 + return $this; 20 + } 21 + 22 + public function getSubscriptions() { 23 + return $this->subscriptions; 24 + } 25 + 26 + public function render() { 27 + $subscriptions = $this->getSubscriptions(); 28 + $handles = $this->getHandles(); 29 + $viewer = $this->getUser(); 30 + 31 + $rows = array(); 32 + $rowc = array(); 33 + foreach ($subscriptions as $subscription) { 34 + $subscription_link = $handles[$subscription->getPHID()]->renderLink(); 35 + $rows[] = array( 36 + $subscription->getID(), 37 + phabricator_datetime($subscription->getDateCreated(), $viewer), 38 + ); 39 + } 40 + 41 + $table = id(new AphrontTableView($rows)) 42 + ->setHeaders( 43 + array( 44 + pht('ID'), 45 + pht('Created'), 46 + )) 47 + ->setColumnClasses( 48 + array( 49 + '', 50 + 'right', 51 + )); 52 + 53 + return $table; 54 + } 55 + 56 + }