@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Move Phortune product logic into applications

Summary: Ref T2787. `Product` is currently a fairly heavy object, but as Phortune develops it makes a lot of sense to make it a lighter object and put more product logic in applications. Convert it into a fairly lightweight reference to applications. The idea is that Phortune is mostly providing a cart flow, and applications manage the details of products.

Test Plan: Funded an initiative for $1.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T2787

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

+338 -460
+22
resources/sql/autopatches/20141005.phortuneproduct.sql
··· 1 + DROP TABLE {$NAMESPACE}_phortune.phortune_producttransaction; 2 + 3 + ALTER TABLE {$NAMESPACE}_phortune.phortune_product 4 + DROP productName; 5 + 6 + ALTER TABLE {$NAMESPACE}_phortune.phortune_product 7 + DROP priceAsCurrency; 8 + 9 + ALTER TABLE {$NAMESPACE}_phortune.phortune_product 10 + ADD productClassKey BINARY(12) NOT NULL; 11 + 12 + ALTER TABLE {$NAMESPACE}_phortune.phortune_product 13 + ADD productClass VARCHAR(128) NOT NULL COLLATE utf8_bin; 14 + 15 + ALTER TABLE {$NAMESPACE}_phortune.phortune_product 16 + ADD productRefKey BINARY(12) NOT NULL; 17 + 18 + ALTER TABLE {$NAMESPACE}_phortune.phortune_product 19 + ADD productRef VARCHAR(128) NOT NULL COLLATE utf8_bin; 20 + 21 + ALTER TABLE {$NAMESPACE}_phortune.phortune_product 22 + ADD UNIQUE KEY `key_product` (productClassKey, productRefKey);
+3 -10
src/__phutil_library_map__.php
··· 669 669 'FundBackerEditor' => 'applications/fund/editor/FundBackerEditor.php', 670 670 'FundBackerListController' => 'applications/fund/controller/FundBackerListController.php', 671 671 'FundBackerPHIDType' => 'applications/fund/phid/FundBackerPHIDType.php', 672 + 'FundBackerProduct' => 'applications/fund/phortune/FundBackerProduct.php', 672 673 'FundBackerQuery' => 'applications/fund/query/FundBackerQuery.php', 673 674 'FundBackerSearchEngine' => 'applications/fund/query/FundBackerSearchEngine.php', 674 675 'FundBackerTransaction' => 'applications/fund/storage/FundBackerTransaction.php', ··· 2580 2581 'PhortunePaymentProviderTestCase' => 'applications/phortune/provider/__tests__/PhortunePaymentProviderTestCase.php', 2581 2582 'PhortunePaypalPaymentProvider' => 'applications/phortune/provider/PhortunePaypalPaymentProvider.php', 2582 2583 'PhortuneProduct' => 'applications/phortune/storage/PhortuneProduct.php', 2583 - 'PhortuneProductEditController' => 'applications/phortune/controller/PhortuneProductEditController.php', 2584 - 'PhortuneProductEditor' => 'applications/phortune/editor/PhortuneProductEditor.php', 2584 + 'PhortuneProductImplementation' => 'applications/phortune/product/PhortuneProductImplementation.php', 2585 2585 'PhortuneProductListController' => 'applications/phortune/controller/PhortuneProductListController.php', 2586 - 'PhortuneProductPurchaseController' => 'applications/phortune/controller/PhortuneProductPurchaseController.php', 2587 2586 'PhortuneProductQuery' => 'applications/phortune/query/PhortuneProductQuery.php', 2588 - 'PhortuneProductTransaction' => 'applications/phortune/storage/PhortuneProductTransaction.php', 2589 - 'PhortuneProductTransactionQuery' => 'applications/phortune/query/PhortuneProductTransactionQuery.php', 2590 2587 'PhortuneProductViewController' => 'applications/phortune/controller/PhortuneProductViewController.php', 2591 2588 'PhortuneProviderController' => 'applications/phortune/controller/PhortuneProviderController.php', 2592 2589 'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php', ··· 3520 3517 'FundBackerEditor' => 'PhabricatorApplicationTransactionEditor', 3521 3518 'FundBackerListController' => 'FundController', 3522 3519 'FundBackerPHIDType' => 'PhabricatorPHIDType', 3520 + 'FundBackerProduct' => 'PhortuneProductImplementation', 3523 3521 'FundBackerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3524 3522 'FundBackerSearchEngine' => 'PhabricatorApplicationSearchEngine', 3525 3523 'FundBackerTransaction' => 'PhabricatorApplicationTransaction', ··· 5616 5614 'PhortuneDAO', 5617 5615 'PhabricatorPolicyInterface', 5618 5616 ), 5619 - 'PhortuneProductEditController' => 'PhabricatorController', 5620 - 'PhortuneProductEditor' => 'PhabricatorApplicationTransactionEditor', 5621 5617 'PhortuneProductListController' => 'PhabricatorController', 5622 - 'PhortuneProductPurchaseController' => 'PhortuneController', 5623 5618 'PhortuneProductQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5624 - 'PhortuneProductTransaction' => 'PhabricatorApplicationTransaction', 5625 - 'PhortuneProductTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 5626 5619 'PhortuneProductViewController' => 'PhortuneController', 5627 5620 'PhortuneProviderController' => 'PhortuneController', 5628 5621 'PhortunePurchase' => array(
+15 -4
src/applications/fund/controller/FundInitiativeBackController.php
··· 60 60 ->setAmountAsCurrency($currency) 61 61 ->save(); 62 62 63 - // TODO: Here, we'd create a purchase and cart. 63 + $product = id(new PhortuneProductQuery()) 64 + ->setViewer($viewer) 65 + ->withClassAndRef('FundBackerProduct', $initiative->getPHID()) 66 + ->executeOne(); 67 + 68 + $account = PhortuneAccountQuery::loadActiveAccountForUser( 69 + $viewer, 70 + PhabricatorContentSource::newFromRequest($request)); 71 + 72 + $cart = $account->newCart($viewer); 73 + 74 + $purchase = $cart->newPurchase($viewer, $product); 75 + $purchase->setBasePriceAsCurrency($currency)->save(); 64 76 65 77 $xactions = array(); 66 78 ··· 74 86 75 87 $editor->applyTransactions($backer, $xactions); 76 88 77 - // TODO: Here, we'd ship the user into Phortune. 78 - 79 - return id(new AphrontRedirectResponse())->setURI($initiative_uri); 89 + return id(new AphrontRedirectResponse()) 90 + ->setURI($cart->getCheckoutURI()); 80 91 } 81 92 } 82 93
+64
src/applications/fund/phortune/FundBackerProduct.php
··· 1 + <?php 2 + 3 + final class FundBackerProduct extends PhortuneProductImplementation { 4 + 5 + private $initiativePHID; 6 + private $initiative; 7 + 8 + public function getRef() { 9 + return $this->getInitiativePHID(); 10 + } 11 + 12 + public function getName(PhortuneProduct $product) { 13 + return pht('Back Initiative %s', $this->initiativePHID); 14 + } 15 + 16 + public function getPriceAsCurrency(PhortuneProduct $product) { 17 + return PhortuneCurrency::newEmptyCurrency(); 18 + } 19 + 20 + public function setInitiativePHID($initiative_phid) { 21 + $this->initiativePHID = $initiative_phid; 22 + return $this; 23 + } 24 + 25 + public function getInitiativePHID() { 26 + return $this->initiativePHID; 27 + } 28 + 29 + public function setInitiative(FundInitiative $initiative) { 30 + $this->initiative = $initiative; 31 + return $this; 32 + } 33 + 34 + public function getInitiative() { 35 + return $this->initiative; 36 + } 37 + 38 + public function loadImplementationsForRefs( 39 + PhabricatorUser $viewer, 40 + array $refs) { 41 + 42 + $initiatives = id(new FundInitiativeQuery()) 43 + ->setViewer($viewer) 44 + ->withPHIDs($refs) 45 + ->execute(); 46 + $initiatives = mpull($initiatives, null, 'getPHID'); 47 + 48 + $objects = array(); 49 + foreach ($refs as $ref) { 50 + $object = id(new FundBackerProduct()) 51 + ->setInitiativePHID($ref); 52 + 53 + $initiative = idx($initiatives, $ref); 54 + if ($initiative) { 55 + $object->setInitiative($initiative); 56 + } 57 + 58 + $objects[] = $object; 59 + } 60 + 61 + return $objects; 62 + } 63 + 64 + }
-3
src/applications/phortune/application/PhabricatorPhortuneApplication.php
··· 58 58 'view/(?P<id>\d+)/' => 'PhortuneProductViewController', 59 59 'edit/(?:(?P<id>\d+)/)?' => 'PhortuneProductEditController', 60 60 ), 61 - 'purchase/(?P<id>\d+)/' => array( 62 - '' => 'PhortunePurchaseViewController', 63 - ), 64 61 'provider/(?P<digest>[^/]+)/(?P<action>[^/]+)/' 65 62 => 'PhortuneProviderController', 66 63 ),
+3 -47
src/applications/phortune/controller/PhortuneController.php
··· 3 3 abstract class PhortuneController extends PhabricatorController { 4 4 5 5 protected function loadActiveAccount(PhabricatorUser $user) { 6 - $accounts = id(new PhortuneAccountQuery()) 7 - ->setViewer($user) 8 - ->withMemberPHIDs(array($user->getPHID())) 9 - ->execute(); 10 - 11 - if (!$accounts) { 12 - return $this->createUserAccount($user); 13 - } else if (count($accounts) == 1) { 14 - return head($accounts); 15 - } else { 16 - throw new Exception('TODO: No account selection yet.'); 17 - } 18 - } 19 - 20 - protected function createUserAccount(PhabricatorUser $user) { 21 - $request = $this->getRequest(); 22 - 23 - $xactions = array(); 24 - $xactions[] = id(new PhortuneAccountTransaction()) 25 - ->setTransactionType(PhortuneAccountTransaction::TYPE_NAME) 26 - ->setNewValue(pht('Account (%s)', $user->getUserName())); 27 - 28 - $xactions[] = id(new PhortuneAccountTransaction()) 29 - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 30 - ->setMetadataValue( 31 - 'edge:type', 32 - PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER) 33 - ->setNewValue( 34 - array( 35 - '=' => array($user->getPHID() => $user->getPHID()), 36 - )); 37 - 38 - $account = id(new PhortuneAccount()) 39 - ->attachMemberPHIDs(array()); 40 - 41 - $editor = id(new PhortuneAccountEditor()) 42 - ->setActor($user) 43 - ->setContentSourceFromRequest($request); 44 - 45 - // We create an account for you the first time you visit Phortune. 46 - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 47 - 48 - $editor->applyTransactions($account, $xactions); 49 - 50 - unset($unguarded); 51 - 52 - return $account; 6 + return PhortuneAccountQuery::loadActiveAccountForUser( 7 + $user, 8 + PhabricatorContentSource::newFromRequest($this->getRequest())); 53 9 } 54 10 55 11 protected function buildChargesTable(array $charges, $show_cart = true) {
-133
src/applications/phortune/controller/PhortuneProductEditController.php
··· 1 - <?php 2 - 3 - final class PhortuneProductEditController extends PhabricatorController { 4 - 5 - private $productID; 6 - 7 - public function willProcessRequest(array $data) { 8 - $this->productID = idx($data, 'id'); 9 - } 10 - 11 - public function processRequest() { 12 - $request = $this->getRequest(); 13 - $user = $request->getUser(); 14 - 15 - if ($this->productID) { 16 - $product = id(new PhortuneProductQuery()) 17 - ->setViewer($user) 18 - ->withIDs(array($this->productID)) 19 - ->executeOne(); 20 - if (!$product) { 21 - return new Aphront404Response(); 22 - } 23 - 24 - $is_create = false; 25 - $cancel_uri = $this->getApplicationURI( 26 - 'product/view/'.$this->productID.'/'); 27 - } else { 28 - $product = PhortuneProduct::initializeNewProduct(); 29 - $is_create = true; 30 - $cancel_uri = $this->getApplicationURI('product/'); 31 - } 32 - 33 - $v_name = $product->getProductName(); 34 - $v_price = $product->getPriceAsCurrency()->formatForDisplay(); 35 - $display_price = $v_price; 36 - 37 - $e_name = true; 38 - $e_price = true; 39 - $errors = array(); 40 - 41 - if ($request->isFormPost()) { 42 - $v_name = $request->getStr('name'); 43 - if (!strlen($v_name)) { 44 - $e_name = pht('Required'); 45 - $errors[] = pht('Product must have a name.'); 46 - } else { 47 - $e_name = null; 48 - } 49 - 50 - $display_price = $request->getStr('price'); 51 - try { 52 - $v_price = PhortuneCurrency::newFromUserInput($user, $display_price) 53 - ->serializeForStorage(); 54 - $e_price = null; 55 - } catch (Exception $ex) { 56 - $errors[] = pht('Price should be formatted as: $1.23'); 57 - $e_price = pht('Invalid'); 58 - } 59 - 60 - if (!$errors) { 61 - $xactions = array(); 62 - 63 - $xactions[] = id(new PhortuneProductTransaction()) 64 - ->setTransactionType(PhortuneProductTransaction::TYPE_NAME) 65 - ->setNewValue($v_name); 66 - 67 - $xactions[] = id(new PhortuneProductTransaction()) 68 - ->setTransactionType(PhortuneProductTransaction::TYPE_PRICE) 69 - ->setNewValue($v_price); 70 - 71 - $editor = id(new PhortuneProductEditor()) 72 - ->setActor($user) 73 - ->setContinueOnNoEffect(true) 74 - ->setContentSourceFromRequest($request); 75 - 76 - $editor->applyTransactions($product, $xactions); 77 - 78 - return id(new AphrontRedirectResponse())->setURI( 79 - $this->getApplicationURI('product/view/'.$product->getID().'/')); 80 - } 81 - } 82 - 83 - if ($errors) { 84 - $errors = id(new AphrontErrorView()) 85 - ->setErrors($errors); 86 - } 87 - 88 - $form = id(new AphrontFormView()) 89 - ->setUser($user) 90 - ->appendChild( 91 - id(new AphrontFormTextControl()) 92 - ->setLabel(pht('Name')) 93 - ->setName('name') 94 - ->setValue($v_name) 95 - ->setError($e_name)) 96 - ->appendChild( 97 - id(new AphrontFormTextControl()) 98 - ->setLabel(pht('Price')) 99 - ->setName('price') 100 - ->setValue($display_price) 101 - ->setError($e_price)) 102 - ->appendChild( 103 - id(new AphrontFormSubmitControl()) 104 - ->setValue( 105 - $is_create 106 - ? pht('Create Product') 107 - : pht('Save Product')) 108 - ->addCancelButton($cancel_uri)); 109 - 110 - $title = pht('Edit Product'); 111 - $crumbs = $this->buildApplicationCrumbs(); 112 - $crumbs->addTextCrumb( 113 - pht('Products'), 114 - $this->getApplicationURI('product/')); 115 - $crumbs->addTextCrumb( 116 - $is_create ? pht('Create') : pht('Edit'), 117 - $request->getRequestURI()); 118 - 119 - $box = id(new PHUIObjectBoxView()) 120 - ->setHeaderText(pht('Edit Product')) 121 - ->appendChild($form); 122 - 123 - return $this->buildApplicationPage( 124 - array( 125 - $crumbs, 126 - $box, 127 - ), 128 - array( 129 - 'title' => $title, 130 - )); 131 - } 132 - 133 - }
-71
src/applications/phortune/controller/PhortuneProductPurchaseController.php
··· 1 - <?php 2 - 3 - final class PhortuneProductPurchaseController 4 - extends PhortuneController { 5 - 6 - private $accountID; 7 - private $productID; 8 - 9 - public function willProcessRequest(array $data) { 10 - $this->accountID = $data['accountID']; 11 - $this->productID = $data['productID']; 12 - } 13 - 14 - public function processRequest() { 15 - $request = $this->getRequest(); 16 - $user = $request->getUser(); 17 - 18 - $account = id(new PhortuneAccountQuery()) 19 - ->setViewer($user) 20 - ->withIDs(array($this->accountID)) 21 - ->executeOne(); 22 - if (!$account) { 23 - return new Aphront404Response(); 24 - } 25 - 26 - $account_uri = $this->getApplicationURI($account->getID().'/'); 27 - 28 - $product = id(new PhortuneProductQuery()) 29 - ->setViewer($user) 30 - ->withIDs(array($this->productID)) 31 - ->executeOne(); 32 - if (!$product) { 33 - return new Aphront404Response(); 34 - } 35 - 36 - if ($request->isFormPost()) { 37 - // TODO: Use ApplicationTransations. 38 - 39 - $cart = new PhortuneCart(); 40 - $cart->openTransaction(); 41 - 42 - $cart->setStatus(PhortuneCart::STATUS_READY); 43 - $cart->setAccountPHID($account->getPHID()); 44 - $cart->setAuthorPHID($user->getPHID()); 45 - $cart->save(); 46 - 47 - $purchase = new PhortunePurchase(); 48 - $purchase->setProductPHID($product->getPHID()); 49 - $purchase->setAccountPHID($account->getPHID()); 50 - $purchase->setAuthorPHID($user->getPHID()); 51 - $purchase->setCartPHID($cart->getPHID()); 52 - $purchase->setBasePriceAsCurrency($product->getPriceAsCurrency()); 53 - $purchase->setQuantity(1); 54 - 55 - $purchase->setStatus(PhortunePurchase::STATUS_PENDING); 56 - $purchase->save(); 57 - 58 - $cart->saveTransaction(); 59 - 60 - $cart_id = $cart->getID(); 61 - $cart_uri = $this->getApplicationURI('/cart/'.$cart_id.'/checkout/'); 62 - return id(new AphrontRedirectResponse())->setURI($cart_uri); 63 - } 64 - 65 - return $this->newDialog() 66 - ->setTitle(pht('Purchase Product')) 67 - ->appendParagraph(pht('Really purchase this stuff?')) 68 - ->addSubmitButton(pht('Checkout')) 69 - ->addCancelButton($account_uri); 70 - } 71 - }
+1 -28
src/applications/phortune/controller/PhortuneProductViewController.php
··· 34 34 35 35 $actions = id(new PhabricatorActionListView()) 36 36 ->setUser($user) 37 - ->setObjectURI($request->getRequestURI()) 38 - ->addAction( 39 - id(new PhabricatorActionView()) 40 - ->setName(pht('Edit Product')) 41 - ->setHref($edit_uri) 42 - ->setIcon('fa-pencil')) 43 - ->addAction( 44 - id(new PhabricatorActionView()) 45 - ->setUser($user) 46 - ->setName(pht('Purchase')) 47 - ->setHref($cart_uri) 48 - ->setIcon('fa-shopping-cart') 49 - ->setWorkflow(true)); 37 + ->setObjectURI($request->getRequestURI()); 50 38 51 39 $crumbs = $this->buildApplicationCrumbs(); 52 40 $crumbs->setActionList($actions); ··· 64 52 pht('Price'), 65 53 $product->getPriceAsCurrency()->formatForDisplay()); 66 54 67 - $xactions = id(new PhortuneProductTransactionQuery()) 68 - ->setViewer($user) 69 - ->withObjectPHIDs(array($product->getPHID())) 70 - ->execute(); 71 - 72 - $engine = id(new PhabricatorMarkupEngine()) 73 - ->setViewer($user); 74 - 75 - $xaction_view = id(new PhabricatorApplicationTransactionView()) 76 - ->setUser($user) 77 - ->setObjectPHID($product->getPHID()) 78 - ->setTransactions($xactions) 79 - ->setMarkupEngine($engine); 80 - 81 55 $object_box = id(new PHUIObjectBoxView()) 82 56 ->setHeader($header) 83 57 ->addPropertyList($properties); ··· 86 60 array( 87 61 $crumbs, 88 62 $object_box, 89 - $xaction_view, 90 63 ), 91 64 array( 92 65 'title' => $title,
-74
src/applications/phortune/editor/PhortuneProductEditor.php
··· 1 - <?php 2 - 3 - 4 - final class PhortuneProductEditor 5 - extends PhabricatorApplicationTransactionEditor { 6 - 7 - public function getEditorApplicationClass() { 8 - return 'PhabricatorPhortuneApplication'; 9 - } 10 - 11 - public function getEditorObjectsDescription() { 12 - return pht('Phortune Products'); 13 - } 14 - 15 - public function getTransactionTypes() { 16 - $types = parent::getTransactionTypes(); 17 - 18 - $types[] = PhortuneProductTransaction::TYPE_NAME; 19 - $types[] = PhortuneProductTransaction::TYPE_PRICE; 20 - 21 - return $types; 22 - } 23 - 24 - 25 - protected function getCustomTransactionOldValue( 26 - PhabricatorLiskDAO $object, 27 - PhabricatorApplicationTransaction $xaction) { 28 - switch ($xaction->getTransactionType()) { 29 - case PhortuneProductTransaction::TYPE_NAME: 30 - return $object->getProductName(); 31 - case PhortuneProductTransaction::TYPE_PRICE: 32 - return $object->getPriceAsCurrency()->serializeForStorage(); 33 - } 34 - return parent::getCustomTransactionOldValue($object, $xaction); 35 - } 36 - 37 - protected function getCustomTransactionNewValue( 38 - PhabricatorLiskDAO $object, 39 - PhabricatorApplicationTransaction $xaction) { 40 - switch ($xaction->getTransactionType()) { 41 - case PhortuneProductTransaction::TYPE_NAME: 42 - case PhortuneProductTransaction::TYPE_PRICE: 43 - return $xaction->getNewValue(); 44 - } 45 - return parent::getCustomTransactionNewValue($object, $xaction); 46 - } 47 - 48 - protected function applyCustomInternalTransaction( 49 - PhabricatorLiskDAO $object, 50 - PhabricatorApplicationTransaction $xaction) { 51 - switch ($xaction->getTransactionType()) { 52 - case PhortuneProductTransaction::TYPE_NAME: 53 - $object->setProductName($xaction->getNewValue()); 54 - return; 55 - case PhortuneProductTransaction::TYPE_PRICE: 56 - $object->setPriceAsCurrency( 57 - PhortuneCurrency::newFromString($xaction->getNewValue())); 58 - return; 59 - } 60 - return parent::applyCustomInternalTransaction($object, $xaction); 61 - } 62 - 63 - protected function applyCustomExternalTransaction( 64 - PhabricatorLiskDAO $object, 65 - PhabricatorApplicationTransaction $xaction) { 66 - switch ($xaction->getTransactionType()) { 67 - case PhortuneProductTransaction::TYPE_NAME: 68 - case PhortuneProductTransaction::TYPE_PRICE: 69 - return; 70 - } 71 - return parent::applyCustomExternalTransaction($object, $xaction); 72 - } 73 - 74 - }
+13
src/applications/phortune/product/PhortuneProductImplementation.php
··· 1 + <?php 2 + 3 + abstract class PhortuneProductImplementation { 4 + 5 + abstract public function loadImplementationsForRefs( 6 + PhabricatorUser $viewer, 7 + array $refs); 8 + 9 + abstract public function getRef(); 10 + abstract public function getName(PhortuneProduct $product); 11 + abstract public function getPriceAsCurrency(PhortuneProduct $product); 12 + 13 + }
+18
src/applications/phortune/query/PhortuneAccountQuery.php
··· 7 7 private $phids; 8 8 private $memberPHIDs; 9 9 10 + public static function loadActiveAccountForUser( 11 + PhabricatorUser $user, 12 + PhabricatorContentSource $content_source) { 13 + 14 + $accounts = id(new PhortuneAccountQuery()) 15 + ->setViewer($user) 16 + ->withMemberPHIDs(array($user->getPHID())) 17 + ->execute(); 18 + 19 + if (!$accounts) { 20 + return PhortuneAccount::createNewAccount($user, $content_source); 21 + } else if (count($accounts) == 1) { 22 + return head($accounts); 23 + } else { 24 + throw new Exception('TODO: No account selection yet.'); 25 + } 26 + } 27 + 10 28 public function withIDs(array $ids) { 11 29 $this->ids = $ids; 12 30 return $this;
+63 -3
src/applications/phortune/query/PhortuneProductQuery.php
··· 5 5 6 6 private $ids; 7 7 private $phids; 8 + private $refMap; 8 9 9 10 public function withIDs(array $ids) { 10 11 $this->ids = $ids; ··· 16 17 return $this; 17 18 } 18 19 20 + public function withClassAndRef($class, $ref) { 21 + $this->refMap = array($class => array($ref)); 22 + return $this; 23 + } 24 + 19 25 protected function loadPage() { 20 26 $table = new PhortuneProduct(); 21 27 $conn = $table->establishConnection('r'); ··· 28 34 $this->buildOrderClause($conn), 29 35 $this->buildLimitClause($conn)); 30 36 31 - return $table->loadAllFromArray($rows); 37 + $page = $table->loadAllFromArray($rows); 38 + 39 + // NOTE: We're loading product implementations here, but also creating any 40 + // products which do not yet exist. 41 + 42 + $class_map = mgroup($page, 'getProductClass'); 43 + if ($this->refMap) { 44 + $class_map += array_fill_keys(array_keys($this->refMap), array()); 45 + } 46 + 47 + foreach ($class_map as $class => $products) { 48 + $refs = mpull($products, null, 'getProductRef'); 49 + if (isset($this->refMap[$class])) { 50 + $refs += array_fill_keys($this->refMap[$class], null); 51 + } 52 + 53 + $implementations = newv($class, array())->loadImplementationsForRefs( 54 + $this->getViewer(), 55 + array_keys($refs)); 56 + $implementations = mpull($implementations, null, 'getRef'); 57 + 58 + foreach ($implementations as $ref => $implementation) { 59 + $product = idx($refs, $ref); 60 + if ($product === null) { 61 + // If this product does not exist yet, create it and add it to the 62 + // result page. 63 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 64 + $product = PhortuneProduct::initializeNewProduct() 65 + ->setProductClass($class) 66 + ->setProductRef($ref) 67 + ->save(); 68 + unset($unguarded); 69 + 70 + $page[] = $product; 71 + } 72 + 73 + $product->attachImplementation($implementation); 74 + } 75 + } 76 + 77 + return $page; 32 78 } 33 79 34 80 private function buildWhereClause(AphrontDatabaseConnection $conn) { 35 81 $where = array(); 36 82 37 - if ($this->ids) { 83 + if ($this->ids !== null) { 38 84 $where[] = qsprintf( 39 85 $conn, 40 86 'id IN (%Ld)', 41 87 $this->ids); 42 88 } 43 89 44 - if ($this->phids) { 90 + if ($this->phids !== null) { 45 91 $where[] = qsprintf( 46 92 $conn, 47 93 'phid IN (%Ls)', 48 94 $this->phids); 95 + } 96 + 97 + if ($this->refMap !== null) { 98 + $sql = array(); 99 + foreach ($this->refMap as $class => $refs) { 100 + foreach ($refs as $ref) { 101 + $sql[] = qsprintf( 102 + $conn, 103 + '(productClassKey = %s AND productRefKey = %s)', 104 + PhabricatorHash::digestForIndex($class), 105 + PhabricatorHash::digestForIndex($ref)); 106 + } 107 + } 108 + $where[] = implode(' OR ', $sql); 49 109 } 50 110 51 111 $where[] = $this->buildPagingClause($conn);
-10
src/applications/phortune/query/PhortuneProductTransactionQuery.php
··· 1 - <?php 2 - 3 - final class PhortuneProductTransactionQuery 4 - extends PhabricatorApplicationTransactionQuery { 5 - 6 - public function getTemplateApplicationTransaction() { 7 - return new PhortuneProductTransaction(); 8 - } 9 - 10 - }
+48
src/applications/phortune/storage/PhortuneAccount.php
··· 13 13 14 14 private $memberPHIDs = self::ATTACHABLE; 15 15 16 + public static function initializeNewAccount(PhabricatorUser $actor) { 17 + $account = id(new PhortuneAccount()); 18 + 19 + $account->memberPHIDs = array(); 20 + 21 + return $account; 22 + } 23 + 24 + public static function createNewAccount( 25 + PhabricatorUser $actor, 26 + PhabricatorContentSource $content_source) { 27 + 28 + $account = PhortuneAccount::initializeNewAccount($actor); 29 + 30 + $xactions = array(); 31 + $xactions[] = id(new PhortuneAccountTransaction()) 32 + ->setTransactionType(PhortuneAccountTransaction::TYPE_NAME) 33 + ->setNewValue(pht('Account (%s)', $actor->getUserName())); 34 + 35 + $xactions[] = id(new PhortuneAccountTransaction()) 36 + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 37 + ->setMetadataValue( 38 + 'edge:type', 39 + PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER) 40 + ->setNewValue( 41 + array( 42 + '=' => array($actor->getPHID() => $actor->getPHID()), 43 + )); 44 + 45 + $editor = id(new PhortuneAccountEditor()) 46 + ->setActor($actor) 47 + ->setContentSource($content_source); 48 + 49 + // We create an account for you the first time you visit Phortune. 50 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 51 + 52 + $editor->applyTransactions($account, $xactions); 53 + 54 + unset($unguarded); 55 + 56 + return $account; 57 + } 58 + 59 + public function newCart(PhabricatorUser $actor) { 60 + return PhortuneCart::initializeNewCart($actor, $this) 61 + ->save(); 62 + } 63 + 16 64 public function getConfiguration() { 17 65 return array( 18 66 self::CONFIG_AUX_PHID => true,
+32
src/applications/phortune/storage/PhortuneCart.php
··· 15 15 private $account = self::ATTACHABLE; 16 16 private $purchases = self::ATTACHABLE; 17 17 18 + public static function initializeNewCart( 19 + PhabricatorUser $actor, 20 + PhortuneAccount $account) { 21 + $cart = id(new PhortuneCart()) 22 + ->setAuthorPHID($actor->getPHID()) 23 + ->setStatus(self::STATUS_READY) 24 + ->setAccountPHID($account->getPHID()); 25 + 26 + $cart->account = $account; 27 + $cart->purchases = array(); 28 + 29 + return $cart; 30 + } 31 + 32 + public function newPurchase( 33 + PhabricatorUser $actor, 34 + PhortuneProduct $product) { 35 + 36 + $purchase = PhortunePurchase::initializeNewPurchase($actor, $product) 37 + ->setAccountPHID($this->getAccount()->getPHID()) 38 + ->setCartPHID($this->getPHID()) 39 + ->save(); 40 + 41 + $this->purchases[] = $purchase; 42 + 43 + return $purchase; 44 + } 45 + 46 + public function getCheckoutURI() { 47 + return '/phortune/cart/'.$this->getID().'/checkout/'; 48 + } 49 + 18 50 public function getConfiguration() { 19 51 return array( 20 52 self::CONFIG_AUX_PHID => true,
+44 -13
src/applications/phortune/storage/PhortuneProduct.php
··· 6 6 final class PhortuneProduct extends PhortuneDAO 7 7 implements PhabricatorPolicyInterface { 8 8 9 - protected $productName; 10 - protected $priceAsCurrency; 11 - protected $metadata; 9 + protected $productClassKey; 10 + protected $productClass; 11 + protected $productRefKey; 12 + protected $productRef; 13 + protected $metadata = array(); 14 + 15 + private $implementation = self::ATTACHABLE; 12 16 13 17 public function getConfiguration() { 14 18 return array( ··· 16 20 self::CONFIG_SERIALIZATION => array( 17 21 'metadata' => self::SERIALIZATION_JSON, 18 22 ), 19 - self::CONFIG_APPLICATION_SERIALIZERS => array( 20 - 'priceAsCurrency' => new PhortuneCurrencySerializer(), 23 + self::CONFIG_COLUMN_SCHEMA => array( 24 + 'productClassKey' => 'bytes12', 25 + 'productClass' => 'text128', 26 + 'productRefKey' => 'bytes12', 27 + 'productRef' => 'text128', 21 28 ), 22 - self::CONFIG_COLUMN_SCHEMA => array( 23 - 'productName' => 'text255', 24 - 'status' => 'text64', 25 - 'priceAsCurrency' => 'text64', 26 - 'billingIntervalInMonths' => 'uint32?', 27 - 'trialPeriodInDays' => 'uint32?', 29 + self::CONFIG_KEY_SCHEMA => array( 30 + 'key_product' => array( 31 + 'columns' => array('productClassKey', 'productRefKey'), 32 + 'unique' => true, 33 + ), 28 34 ), 29 35 ) + parent::getConfiguration(); 30 36 } ··· 35 41 } 36 42 37 43 public static function initializeNewProduct() { 38 - return id(new PhortuneProduct()) 39 - ->setPriceAsCurrency(PhortuneCurrency::newEmptyCurrency()); 44 + return id(new PhortuneProduct()); 45 + } 46 + 47 + public function attachImplementation(PhortuneProductImplementation $impl) { 48 + $this->implementation = $impl; 49 + } 50 + 51 + public function getImplementation() { 52 + return $this->assertAttached($this->implementation); 53 + } 54 + 55 + public function save() { 56 + $this->productClassKey = PhabricatorHash::digestForIndex( 57 + $this->productClass); 58 + 59 + $this->productRefKey = PhabricatorHash::digestForIndex( 60 + $this->productRef); 61 + 62 + return parent::save(); 63 + } 64 + 65 + public function getPriceAsCurrency() { 66 + return $this->getImplementation()->getPriceAsCurrency($this); 67 + } 68 + 69 + public function getProductName() { 70 + return $this->getImplementation()->getName($this); 40 71 } 41 72 42 73
-63
src/applications/phortune/storage/PhortuneProductTransaction.php
··· 1 - <?php 2 - 3 - final class PhortuneProductTransaction 4 - extends PhabricatorApplicationTransaction { 5 - 6 - const TYPE_NAME = 'product:name'; 7 - const TYPE_PRICE = 'product:price'; 8 - 9 - public function getApplicationName() { 10 - return 'phortune'; 11 - } 12 - 13 - public function getApplicationTransactionType() { 14 - return PhabricatorPHIDConstants::PHID_TYPE_PDCT; 15 - } 16 - 17 - public function getApplicationTransactionCommentObject() { 18 - return null; 19 - } 20 - 21 - public function getTitle() { 22 - $author_phid = $this->getAuthorPHID(); 23 - 24 - $old = $this->getOldValue(); 25 - $new = $this->getNewValue(); 26 - 27 - switch ($this->getTransactionType()) { 28 - case self::TYPE_NAME: 29 - if ($old === null) { 30 - return pht( 31 - '%s created this product.', 32 - $this->renderHandleLink($author_phid)); 33 - } else { 34 - return pht( 35 - '%s renamed this product from "%s" to "%s".', 36 - $this->renderHandleLink($author_phid), 37 - $old, 38 - $new); 39 - } 40 - break; 41 - case self::TYPE_PRICE: 42 - if ($old === null) { 43 - return pht( 44 - '%s set product price to %s.', 45 - $this->renderHandleLink($author_phid), 46 - PhortuneCurrency::newFromString($new) 47 - ->formatForDisplay()); 48 - } else { 49 - return pht( 50 - '%s changed product price from %s to %s.', 51 - $this->renderHandleLink($author_phid), 52 - PhortuneCurrency::newFromString($old) 53 - ->formatForDisplay(), 54 - PhortuneCurrency::newFromString($new) 55 - ->formatForDisplay()); 56 - } 57 - break; 58 - } 59 - 60 - return parent::getTitle(); 61 - } 62 - 63 - }
+12 -1
src/applications/phortune/storage/PhortunePurchase.php
··· 20 20 protected $basePriceAsCurrency; 21 21 protected $quantity; 22 22 protected $status; 23 - protected $metadata; 23 + protected $metadata = array(); 24 24 25 25 private $cart = self::ATTACHABLE; 26 + 27 + public static function initializeNewPurchase( 28 + PhabricatorUser $actor, 29 + PhortuneProduct $product) { 30 + return id(new PhortunePurchase()) 31 + ->setAuthorPHID($actor->getPHID()) 32 + ->setProductPHID($product->getPHID()) 33 + ->setQuantity(1) 34 + ->setStatus(self::STATUS_PENDING) 35 + ->setBasePriceAsCurrency($product->getPriceAsCurrency()); 36 + } 26 37 27 38 public function getConfiguration() { 28 39 return array(