From 960ac3b2a6e1033e1ccc6a72ea1b4bca16647abe Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 28 Mar 2013 09:10:34 -0700 Subject: [PATCH] Phortune v0 Summary: Ref T2787. This does very little so far, but makes inroads on accounts and billing. This is mostly just modeled on what Stripe looks like. The objects are: - **Account**: Has one or more authorized users, who can make manage the account. An example might be "Phacility", and the three of us would be able to manage it. A user may be associated with more than one account (e.g., a corporate account and a personal account) but the UI tries to simplify the common case of a single account. - **Payment Method**: Something we can get sweet sweet money from; for now, a credit card registered with Stripe. Payment methods are associated with an account. - **Product**: A good (one time charge) or service (recurring charge). This might be "t-shirt" or "enterprise plan" or "hourly support" or whatever else. - **Purchase**: Represents a user purchasing a Product for an Account, using a Payment Method. e.g., you bought a shirt, or started a plan, or purchased support. - **Charge**: Actual charges against payment methods. A Purchase can create more than one charge if it's a plan, or if the first charge fails and we re-bill. This doesn't fully account for stuff like coupons/discounts yet but they should fit into the model without any issues. This only implements `Account`, and that only partially. Test Plan: {F37531} Reviewers: chad, btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T2787 Differential Revision: https://secure.phabricator.com/D5435 --- resources/sql/patches/20130322.phortune.sql | 46 ++++++ src/__phutil_library_map__.php | 40 ++++++ ...AphrontDefaultApplicationConfiguration.php | 6 - .../phid/PhabricatorPHIDConstants.php | 5 + .../PhabricatorApplicationPhortune.php | 53 +++++++ .../PhortuneAccountViewController.php | 131 ++++++++++++++++++ .../controller/PhortuneController.php | 45 ++++++ .../controller/PhortuneLandingController.php | 29 ++++ .../PhortunePaymentMethodListController.php | 23 +++ .../PhortunePaymentMethodViewController.php | 21 +++ .../phortune/editor/PhortuneAccountEditor.php | 62 +++++++++ .../phortune/query/PhortuneAccountQuery.php | 102 ++++++++++++++ .../query/PhortuneAccountTransactionQuery.php | 10 ++ .../phortune/storage/PhortuneAccount.php | 60 ++++++++ .../storage/PhortuneAccountTransaction.php | 74 ++++++++++ .../phortune/storage/PhortuneCharge.php | 38 +++++ .../phortune/storage/PhortuneDAO.php | 9 ++ .../storage/PhortunePaymentMethod.php | 64 +++++++++ .../phortune/storage/PhortuneProduct.php | 34 +++++ .../phortune/storage/PhortunePurchase.php | 35 +++++ .../edges/constants/PhabricatorEdgeConfig.php | 9 +- .../patch/PhabricatorBuiltinPatchList.php | 8 ++ 22 files changed, 897 insertions(+), 7 deletions(-) create mode 100644 resources/sql/patches/20130322.phortune.sql create mode 100644 src/applications/phortune/application/PhabricatorApplicationPhortune.php create mode 100644 src/applications/phortune/controller/PhortuneAccountViewController.php create mode 100644 src/applications/phortune/controller/PhortuneController.php create mode 100644 src/applications/phortune/controller/PhortuneLandingController.php create mode 100644 src/applications/phortune/controller/PhortunePaymentMethodListController.php create mode 100644 src/applications/phortune/controller/PhortunePaymentMethodViewController.php create mode 100644 src/applications/phortune/editor/PhortuneAccountEditor.php create mode 100644 src/applications/phortune/query/PhortuneAccountQuery.php create mode 100644 src/applications/phortune/query/PhortuneAccountTransactionQuery.php create mode 100644 src/applications/phortune/storage/PhortuneAccount.php create mode 100644 src/applications/phortune/storage/PhortuneAccountTransaction.php create mode 100644 src/applications/phortune/storage/PhortuneCharge.php create mode 100644 src/applications/phortune/storage/PhortuneDAO.php create mode 100644 src/applications/phortune/storage/PhortunePaymentMethod.php create mode 100644 src/applications/phortune/storage/PhortuneProduct.php create mode 100644 src/applications/phortune/storage/PhortunePurchase.php diff --git a/resources/sql/patches/20130322.phortune.sql b/resources/sql/patches/20130322.phortune.sql new file mode 100644 index 0000000000..5105716972 --- /dev/null +++ b/resources/sql/patches/20130322.phortune.sql @@ -0,0 +1,46 @@ +CREATE TABLE {$NAMESPACE}_phortune.phortune_account ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, + name VARCHAR(255) NOT NULL, + balanceInCents BIGINT NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE {$NAMESPACE}_phortune.phortune_accounttransaction ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, + objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, + viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, + editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, + commentPHID VARCHAR(64) COLLATE utf8_bin, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin, + oldValue LONGTEXT NOT NULL COLLATE utf8_bin, + newValue LONGTEXT NOT NULL COLLATE utf8_bin, + contentSource LONGTEXT NOT NULL COLLATE utf8_bin, + metadata LONGTEXT NOT NULL COLLATE utf8_bin, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + KEY `key_object` (objectPHID) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE {$NAMESPACE}_phortune.edge ( + src VARCHAR(64) NOT NULL COLLATE utf8_bin, + type INT UNSIGNED 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 DEFAULT CHARSET=utf8; + +CREATE TABLE {$NAMESPACE}_phortune.edgedata ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + data LONGTEXT NOT NULL COLLATE utf8_bin +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5a6886f6c6..503dce3f3f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -702,6 +702,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationPhame' => 'applications/phame/application/PhabricatorApplicationPhame.php', 'PhabricatorApplicationPhlux' => 'applications/phlux/application/PhabricatorApplicationPhlux.php', 'PhabricatorApplicationPholio' => 'applications/pholio/application/PhabricatorApplicationPholio.php', + 'PhabricatorApplicationPhortune' => 'applications/phortune/application/PhabricatorApplicationPhortune.php', 'PhabricatorApplicationPhriction' => 'applications/phriction/application/PhabricatorApplicationPhriction.php', 'PhabricatorApplicationPonder' => 'applications/ponder/application/PhabricatorApplicationPonder.php', 'PhabricatorApplicationProject' => 'applications/project/application/PhabricatorApplicationProject.php', @@ -1527,7 +1528,22 @@ phutil_register_library_map(array( 'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php', 'PholioTransactionType' => 'applications/pholio/constants/PholioTransactionType.php', 'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php', + 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', + 'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php', + 'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php', + 'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php', + 'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php', + 'PhortuneAccountViewController' => 'applications/phortune/controller/PhortuneAccountViewController.php', + 'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php', + 'PhortuneController' => 'applications/phortune/controller/PhortuneController.php', + 'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php', + 'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php', 'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php', + 'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php', + 'PhortunePaymentMethodListController' => 'applications/phortune/controller/PhortunePaymentMethodListController.php', + 'PhortunePaymentMethodViewController' => 'applications/phortune/controller/PhortunePaymentMethodViewController.php', + 'PhortuneProduct' => 'applications/phortune/storage/PhortuneProduct.php', + 'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php', 'PhortuneStripeBaseController' => 'applications/phortune/stripe/controller/PhortuneStripeBaseController.php', 'PhortuneStripePaymentFormView' => 'applications/phortune/stripe/view/PhortuneStripePaymentFormView.php', 'PhortuneStripeTestPaymentFormController' => 'applications/phortune/stripe/controller/PhortuneStripeTestPaymentFormController.php', @@ -2328,6 +2344,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationPhame' => 'PhabricatorApplication', 'PhabricatorApplicationPhlux' => 'PhabricatorApplication', 'PhabricatorApplicationPholio' => 'PhabricatorApplication', + 'PhabricatorApplicationPhortune' => 'PhabricatorApplication', 'PhabricatorApplicationPhriction' => 'PhabricatorApplication', 'PhabricatorApplicationPonder' => 'PhabricatorApplication', 'PhabricatorApplicationProject' => 'PhabricatorApplication', @@ -3157,7 +3174,30 @@ phutil_register_library_map(array( 'PholioTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PholioTransactionType' => 'PholioConstants', 'PholioTransactionView' => 'PhabricatorApplicationTransactionView', + 'PhortuneAccount' => + array( + 0 => 'PhortuneDAO', + 1 => 'PhabricatorPolicyInterface', + ), + 'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhortuneAccountTransaction' => 'PhabricatorApplicationTransaction', + 'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhortuneAccountViewController' => 'PhortuneController', + 'PhortuneCharge' => 'PhortuneDAO', + 'PhortuneController' => 'PhabricatorController', + 'PhortuneDAO' => 'PhabricatorLiskDAO', + 'PhortuneLandingController' => 'PhortuneController', 'PhortuneMonthYearExpiryControl' => 'AphrontFormControl', + 'PhortunePaymentMethod' => + array( + 0 => 'PhortuneDAO', + 1 => 'PhabricatorPolicyInterface', + ), + 'PhortunePaymentMethodListController' => 'PhabricatorController', + 'PhortunePaymentMethodViewController' => 'PhabricatorController', + 'PhortuneProduct' => 'PhortuneDAO', + 'PhortunePurchase' => 'PhortuneDAO', 'PhortuneStripeBaseController' => 'PhabricatorController', 'PhortuneStripePaymentFormView' => 'AphrontView', 'PhortuneStripeTestPaymentFormController' => 'PhortuneStripeBaseController', diff --git a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php index 2b905038dd..753fffef3a 100644 --- a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php @@ -104,12 +104,6 @@ class AphrontDefaultApplicationConfiguration 'clear/' => 'PhabricatorNotificationClearController', ), - '/phortune/' => array( - 'stripe/' => array( - 'testpaymentform/' => 'PhortuneStripeTestPaymentFormController', - ), - ), - '/debug/' => 'PhabricatorDebugController', ); } diff --git a/src/applications/phid/PhabricatorPHIDConstants.php b/src/applications/phid/PhabricatorPHIDConstants.php index 33ef87cae1..5bd479bda4 100644 --- a/src/applications/phid/PhabricatorPHIDConstants.php +++ b/src/applications/phid/PhabricatorPHIDConstants.php @@ -33,6 +33,11 @@ final class PhabricatorPHIDConstants { const PHID_TYPE_CONF = 'CONF'; const PHID_TYPE_CONP = 'CONP'; const PHID_TYPE_PVAR = 'PVAR'; + const PHID_TYPE_ACNT = 'ACNT'; + const PHID_TYPE_PDCT = 'PDCT'; + const PHID_TYPE_PRCH = 'PRCH'; + const PHID_TYPE_PAYM = 'PAYM'; + const PHID_TYPE_CHRG = 'CHRG'; const PHID_TYPE_XACT = 'XACT'; const PHID_TYPE_XCMT = 'XCMT'; diff --git a/src/applications/phortune/application/PhabricatorApplicationPhortune.php b/src/applications/phortune/application/PhabricatorApplicationPhortune.php new file mode 100644 index 0000000000..99d5b9e17a --- /dev/null +++ b/src/applications/phortune/application/PhabricatorApplicationPhortune.php @@ -0,0 +1,53 @@ + array( + '' => 'PhortuneLandingController', + '(?P\d+)/' => array( + '' => 'PhortuneAccountViewController', + ), + + 'account/' => array( + '' => 'PhortuneAccountListController', + 'edit/(?:(?P\d+)/)?' => 'PhortuneAccountEditController', + ), + 'paymentmethod/' => array( + '' => 'PhortunePaymentMethodListController', + 'view/(?P\d+)/' => 'PhortunePaymentMethodViewController', + 'edit/(?:(?P\d+)/)?' => 'PhortunePaymentMethodEditController', + ), + 'stripe/' => array( + 'testpaymentform/' => 'PhortuneStripeTestPaymentFormController', + ), + ), + ); + } + +} diff --git a/src/applications/phortune/controller/PhortuneAccountViewController.php b/src/applications/phortune/controller/PhortuneAccountViewController.php new file mode 100644 index 0000000000..616914937b --- /dev/null +++ b/src/applications/phortune/controller/PhortuneAccountViewController.php @@ -0,0 +1,131 @@ +accountID = $data['accountID']; + } + + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + + $account = id(new PhortuneAccountQuery()) + ->setViewer($user) + ->withIDs(array($this->accountID)) + ->executeOne(); + + if (!$account) { + return new Aphront404Response(); + } + + $title = $account->getName(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('Account')) + ->setHref($request->getRequestURI())); + + $header = id(new PhabricatorHeaderView()) + ->setHeader($title); + + $actions = id(new PhabricatorActionListView()) + ->setUser($user) + ->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Account')) + ->setIcon('edit') + ->setHref('#') + ->setDisabled(true)) + ->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Members')) + ->setIcon('transcript') + ->setHref('#') + ->setDisabled(true)); + + $properties = id(new PhabricatorPropertyListView()) + ->setObject($account) + ->setUser($user); + + $properties->addProperty(pht('Balance'), $account->getBalanceInCents()); + + $payment_methods = $this->buildPaymentMethodsSection($account); + $account_history = $this->buildAccountHistorySection($account); + + return $this->buildApplicationPage( + array( + $crumbs, + $header, + $actions, + $properties, + $payment_methods, + $account_history, + ), + array( + 'title' => $title, + 'device' => true, + 'dust' => true, + )); + } + + private function buildPaymentMethodsSection(PhortuneAccount $account) { + $request = $this->getRequest(); + $user = $request->getUser(); + + $header = id(new PhabricatorHeaderView()) + ->setHeader(pht('Payment Methods')); + + $id = $account->getID(); + $add_uri = $this->getApplicationURI($id.'/paymentmethod/edit/'); + + $actions = id(new PhabricatorActionListView()) + ->setUser($user) + ->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Add Payment Method')) + ->setIcon('new') + ->setHref($add_uri)); + + $list = id(new PhabricatorObjectItemListView()) + ->setUser($user) + ->setNoDataString( + pht('No payment methods associated with this account.')); + + return array( + $header, + $actions, + $list, + ); + } + + private function buildAccountHistorySection(PhortuneAccount $account) { + $request = $this->getRequest(); + $user = $request->getUser(); + + $header = id(new PhabricatorHeaderView()) + ->setHeader(pht('Account History')); + + $xactions = id(new PhortuneAccountTransactionQuery()) + ->setViewer($user) + ->withObjectPHIDs(array($account->getPHID())) + ->execute(); + + $engine = id(new PhabricatorMarkupEngine()) + ->setViewer($user); + + $xaction_view = id(new PhabricatorApplicationTransactionView()) + ->setUser($user) + ->setTransactions($xactions) + ->setMarkupEngine($engine); + + return array( + $header, + $xaction_view, + ); + } + +} diff --git a/src/applications/phortune/controller/PhortuneController.php b/src/applications/phortune/controller/PhortuneController.php new file mode 100644 index 0000000000..21c2f395ad --- /dev/null +++ b/src/applications/phortune/controller/PhortuneController.php @@ -0,0 +1,45 @@ +getRequest(); + + $xactions = array(); + $xactions[] = id(new PhortuneAccountTransaction()) + ->setTransactionType(PhortuneAccountTransaction::TYPE_NAME) + ->setNewValue(pht('Account (%s)', $user->getUserName())); + + $xactions[] = id(new PhortuneAccountTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue( + 'edge:type', + PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER) + ->setNewValue( + array( + '=' => array($user->getPHID() => $user->getPHID()), + )); + + $account = new PhortuneAccount(); + + $editor = id(new PhortuneAccountEditor()) + ->setActor($user) + ->setContentSource( + PhabricatorContentSource::newForSource( + PhabricatorContentSource::SOURCE_WEB, + array( + 'ip' => $request->getRemoteAddr(), + ))); + + // We create an account for you the first time you visit Phortune. + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + + $editor->applyTransactions($account, $xactions); + + unset($unguarded); + + return $account; + } + + +} diff --git a/src/applications/phortune/controller/PhortuneLandingController.php b/src/applications/phortune/controller/PhortuneLandingController.php new file mode 100644 index 0000000000..a44790df88 --- /dev/null +++ b/src/applications/phortune/controller/PhortuneLandingController.php @@ -0,0 +1,29 @@ +getRequest(); + $user = $request->getUser(); + + $accounts = id(new PhortuneAccountQuery()) + ->setViewer($user) + ->withMemberPHIDs(array($user->getPHID())) + ->execute(); + + if (!$accounts) { + $account = $this->createUserAccount($user); + $accounts = array($account); + } + + if (count($accounts) == 1) { + $account = head($accounts); + $next_uri = $this->getApplicationURI($account->getID().'/'); + } else { + $next_uri = $this->getApplicationURI('account/'); + } + + return id(new AphrontRedirectResponse())->setURI($next_uri); + } + +} diff --git a/src/applications/phortune/controller/PhortunePaymentMethodListController.php b/src/applications/phortune/controller/PhortunePaymentMethodListController.php new file mode 100644 index 0000000000..4e4f003890 --- /dev/null +++ b/src/applications/phortune/controller/PhortunePaymentMethodListController.php @@ -0,0 +1,23 @@ +getRequest(); + $user = $request->getUser(); + + $title = pht('Payment Methods'); + $crumbs = $this->buildApplicationCrumbs(); + + return $this->buildApplicationPage( + array( + $crumbs, + ), + array( + 'title' => $title, + 'device' => true, + 'dust' => true, + )); + } + +} diff --git a/src/applications/phortune/controller/PhortunePaymentMethodViewController.php b/src/applications/phortune/controller/PhortunePaymentMethodViewController.php new file mode 100644 index 0000000000..167c152745 --- /dev/null +++ b/src/applications/phortune/controller/PhortunePaymentMethodViewController.php @@ -0,0 +1,21 @@ +buildApplicationCrumbs(); + + return $this->buildApplicationPage( + array( + $crumbs, + ), + array( + 'title' => $title, + 'device' => true, + 'dust' => true, + )); + } + +} diff --git a/src/applications/phortune/editor/PhortuneAccountEditor.php b/src/applications/phortune/editor/PhortuneAccountEditor.php new file mode 100644 index 0000000000..6eb45475c8 --- /dev/null +++ b/src/applications/phortune/editor/PhortuneAccountEditor.php @@ -0,0 +1,62 @@ +getTransactionType()) { + case PhortuneAccountTransaction::TYPE_NAME: + return $object->getName(); + } + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + switch ($xaction->getTransactionType()) { + case PhortuneAccountTransaction::TYPE_NAME: + return $xaction->getNewValue(); + } + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + switch ($xaction->getTransactionType()) { + case PhortuneAccountTransaction::TYPE_NAME: + $object->setName($xaction->getNewValue()); + return; + case PhabricatorTransactions::TYPE_EDGE: + return; + } + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + switch ($xaction->getTransactionType()) { + case PhortuneAccountTransaction::TYPE_NAME: + return; + case PhabricatorTransactions::TYPE_EDGE: + return; + } + return parent::applyCustomExternalTransaction($object, $xaction); + } + +} diff --git a/src/applications/phortune/query/PhortuneAccountQuery.php b/src/applications/phortune/query/PhortuneAccountQuery.php new file mode 100644 index 0000000000..63eab3d200 --- /dev/null +++ b/src/applications/phortune/query/PhortuneAccountQuery.php @@ -0,0 +1,102 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withMemberPHIDs(array $phids) { + $this->memberPHIDs = $phids; + return $this; + } + + protected function loadPage() { + $table = new PhortuneAccount(); + $conn = $table->establishConnection('r'); + + $rows = queryfx_all( + $conn, + 'SELECT a.* FROM %T a %Q %Q %Q %Q', + $table->getTableName(), + $this->buildJoinClause($conn), + $this->buildWhereClause($conn), + $this->buildOrderClause($conn), + $this->buildLimitClause($conn)); + + return $table->loadAllFromArray($rows); + } + + protected function willFilterPage(array $accounts) { + if (!$accounts) { + return array(); + } + + $query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(mpull($accounts, 'getPHID')) + ->withEdgeTypes(array(PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER)); + $query->execute(); + + foreach ($accounts as $account) { + $member_phids = $query->getDestinationPHIDs(array($account->getPHID())); + $account->attachMemberPHIDs($member_phids); + } + + return $accounts; + } + + private function buildWhereClause(AphrontDatabaseConnection $conn) { + $where = array(); + + $where[] = $this->buildPagingClause($conn); + + if ($this->ids) { + $where[] = qsprintf( + $conn, + 'a.id IN (%Ld)', + $this->ids); + } + + if ($this->phids) { + $where[] = qsprintf( + $conn, + 'a.phid IN (%Ls)', + $this->phids); + } + + if ($this->memberPHIDs) { + $where[] = qsprintf( + $conn, + 'm.dst IN (%Ls)', + $this->memberPHIDs); + } + + return $this->formatWhereClause($where); + } + + private function buildJoinClause(AphrontDatabaseConnection $conn) { + $joins = array(); + + if ($this->memberPHIDs) { + $joins[] = qsprintf( + $conn, + 'LEFT JOIN %T m ON a.phid = m.src AND m.type = %d', + PhabricatorEdgeConfig::TABLE_NAME_EDGE, + PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER); + } + + return implode(' ', $joins); + } + +} diff --git a/src/applications/phortune/query/PhortuneAccountTransactionQuery.php b/src/applications/phortune/query/PhortuneAccountTransactionQuery.php new file mode 100644 index 0000000000..da5eaa7eb2 --- /dev/null +++ b/src/applications/phortune/query/PhortuneAccountTransactionQuery.php @@ -0,0 +1,10 @@ + true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorPHIDConstants::PHID_TYPE_ACNT); + } + + public function getMemberPHIDs() { + if ($this->memberPHIDs === null) { + throw new Exception("Call attachMemberPHIDs() before getMemberPHIDs()!"); + } + return $this->memberPHIDs; + } + + public function attachMemberPHIDs(array $phids) { + $this->memberPHIDs = $phids; + return $this; + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + return false; + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + $members = array_fuse($this->getMemberPHIDs()); + return isset($members[$viewer->getPHID()]); + } + +} diff --git a/src/applications/phortune/storage/PhortuneAccountTransaction.php b/src/applications/phortune/storage/PhortuneAccountTransaction.php new file mode 100644 index 0000000000..770c504853 --- /dev/null +++ b/src/applications/phortune/storage/PhortuneAccountTransaction.php @@ -0,0 +1,74 @@ +getAuthorPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_NAME: + if ($old === null) { + return pht( + '%s created this account.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s renamed this account from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + } + break; + case PhabricatorTransactions::TYPE_EDGE: + switch ($this->getMetadataValue('edge:type')) { + case PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER: + $add = array_diff(array_keys($new), array_keys($old)); + $rem = array_diff(array_keys($old), array_keys($new)); + if ($add && $rem) { + return pht( + '%s changed account members, added %s; removed %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleList($add), + $this->renderHandleList($rem)); + } else if ($add) { + return pht( + '%s added account members: %s', + $this->renderHandleLink($author_phid), + $this->renderHandleList($add)); + } else { + return pht( + '%s removed account members: %s', + $this->renderHandleLink($author_phid), + $this->renderHandleList($add)); + } + break; + } + break; + } + + return parent::getTitle(); + } + +} diff --git a/src/applications/phortune/storage/PhortuneCharge.php b/src/applications/phortune/storage/PhortuneCharge.php new file mode 100644 index 0000000000..a53dbec546 --- /dev/null +++ b/src/applications/phortune/storage/PhortuneCharge.php @@ -0,0 +1,38 @@ + true, + self::CONFIG_SERIALIZATION => array( + 'metadata' => self::SERIALIZATION_JSON, + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorPHIDConstants::PHID_TYPE_CHRG); + } + +} diff --git a/src/applications/phortune/storage/PhortuneDAO.php b/src/applications/phortune/storage/PhortuneDAO.php new file mode 100644 index 0000000000..7cd17a10cf --- /dev/null +++ b/src/applications/phortune/storage/PhortuneDAO.php @@ -0,0 +1,9 @@ + true, + self::CONFIG_SERIALIZATION => array( + 'metadata' => self::SERIALIZATION_JSON, + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorPHIDConstants::PHID_TYPE_PAYM); + } + + public function attachAccount(PhortuneAccount $account) { + $this->account = $account; + return $this; + } + + public function getAccount() { + if (!$this->account) { + throw new Exception("Call attachAccount() before getAccount()!"); + } + return $this->account; + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + return $this->getAccount()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getAccount()->hasAutomaticCapability( + $capability, + $viewer); + } + +} diff --git a/src/applications/phortune/storage/PhortuneProduct.php b/src/applications/phortune/storage/PhortuneProduct.php new file mode 100644 index 0000000000..688fdd8cee --- /dev/null +++ b/src/applications/phortune/storage/PhortuneProduct.php @@ -0,0 +1,34 @@ + true, + self::CONFIG_SERIALIZATION => array( + 'metadata' => self::SERIALIZATION_JSON, + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorPHIDConstants::PHID_TYPE_PDCT); + } + + + +} diff --git a/src/applications/phortune/storage/PhortunePurchase.php b/src/applications/phortune/storage/PhortunePurchase.php new file mode 100644 index 0000000000..d3e31d842e --- /dev/null +++ b/src/applications/phortune/storage/PhortunePurchase.php @@ -0,0 +1,35 @@ + true, + self::CONFIG_SERIALIZATION => array( + 'metadata' => self::SERIALIZATION_JSON, + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorPHIDConstants::PHID_TYPE_PRCH); + } + +} diff --git a/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php b/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php index 162341368f..2b36e138ed 100644 --- a/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php +++ b/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php @@ -42,8 +42,12 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants { const TYPE_OBJECT_HAS_FILE = 25; const TYPE_FILE_HAS_OBJECT = 26; + const TYPE_ACCOUNT_HAS_MEMBER = 27; + const TYPE_MEMBER_HAS_ACCOUNT = 28; + const TYPE_TEST_NO_CYCLE = 9000; + public static function getInverse($edge_type) { static $map = array( self::TYPE_TASK_HAS_COMMIT => self::TYPE_COMMIT_HAS_TASK, @@ -84,6 +88,9 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants { self::TYPE_OBJECT_HAS_FILE => self::TYPE_FILE_HAS_OBJECT, self::TYPE_FILE_HAS_OBJECT => self::TYPE_OBJECT_HAS_FILE, + + self::TYPE_ACCOUNT_HAS_MEMBER => self::TYPE_MEMBER_HAS_ACCOUNT, + self::TYPE_MEMBER_HAS_ACCOUNT => self::TYPE_ACCOUNT_HAS_MEMBER, ); return idx($map, $edge_type); @@ -117,7 +124,7 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants { PhabricatorPHIDConstants::PHID_TYPE_MCRO => 'PhabricatorFileImageMacro', PhabricatorPHIDConstants::PHID_TYPE_CONP => 'ConpherenceThread', PhabricatorPHIDConstants::PHID_TYPE_WIKI => 'PhrictionDocument', - + PhabricatorPHIDConstants::PHID_TYPE_ACNT => 'PhortuneAccount', ); $class = idx($class_map, $phid_type); diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index 0bd10daba4..cd0bb98513 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -179,6 +179,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList { 'type' => 'db', 'name' => 'phlux', ), + 'db.phortune' => array( + 'type' => 'db', + 'name' => 'phortune', + ), '0000.legacy.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath('0000.legacy.sql'), @@ -1198,6 +1202,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList { 'type' => 'sql', 'name' => $this->getPatchPath('20130310.xactionmeta.sql'), ), + '20130322.phortune.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20130322.phortune.sql'), + ), ); }