From 803eb29c71d35f956b11d6380bc414d341de826c Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 20 Aug 2019 12:28:52 -0700 Subject: [PATCH 01/27] Fix flag typo in "Managing Caches" documentation Summary: See PHI1392. This flag is `--all`, not `--all-caches`. Test Plan: Ran `bin/cache purge --all`. Differential Revision: https://secure.phabricator.com/D20722 --- src/docs/user/configuration/managing_caches.diviner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/user/configuration/managing_caches.diviner b/src/docs/user/configuration/managing_caches.diviner index 53b48250ea..e873b99d8c 100644 --- a/src/docs/user/configuration/managing_caches.diviner +++ b/src/docs/user/configuration/managing_caches.diviner @@ -41,7 +41,7 @@ with the `--help` flag to see options: This tool can purge caches in a granular way, but it's normally easiest to just purge all of the caches: - phabricator/ $ ./bin/cache purge --purge-all + phabricator/ $ ./bin/cache purge --all You can purge caches safely. The data they contain can always be rebuilt from other data if Phabricator needs it. From 721a86401ff454f524481dd4e5fc893cd6ad8d07 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 20 Aug 2019 12:46:09 -0700 Subject: [PATCH 02/27] Implement "drydock.resource.search" Summary: Fixes T13383. Provide a basic "drydock.resource.search". Also allow "drydock.lease.search" to be queried by resource PHID. Test Plan: Called "drydock.resource.search" and "drydock.lease.search" with various constraints. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13383 Differential Revision: https://secure.phabricator.com/D20723 --- src/__phutil_library_map__.php | 3 ++ .../DrydockResourceSearchConduitAPIMethod.php | 18 +++++++++ .../query/DrydockLeaseSearchEngine.php | 9 +++++ .../drydock/query/DrydockResourceQuery.php | 16 +++++--- .../query/DrydockResourceSearchEngine.php | 10 +++++ .../drydock/storage/DrydockResource.php | 38 ++++++++++++++++++- 6 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 src/applications/drydock/conduit/DrydockResourceSearchConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 03e0163ec8..a0072f7cea 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1222,6 +1222,7 @@ phutil_register_library_map(array( 'DrydockResourceQuery' => 'applications/drydock/query/DrydockResourceQuery.php', 'DrydockResourceReclaimLogType' => 'applications/drydock/logtype/DrydockResourceReclaimLogType.php', 'DrydockResourceReleaseController' => 'applications/drydock/controller/DrydockResourceReleaseController.php', + 'DrydockResourceSearchConduitAPIMethod' => 'applications/drydock/conduit/DrydockResourceSearchConduitAPIMethod.php', 'DrydockResourceSearchEngine' => 'applications/drydock/query/DrydockResourceSearchEngine.php', 'DrydockResourceStatus' => 'applications/drydock/constants/DrydockResourceStatus.php', 'DrydockResourceUpdateWorker' => 'applications/drydock/worker/DrydockResourceUpdateWorker.php', @@ -7021,6 +7022,7 @@ phutil_register_library_map(array( 'DrydockResource' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', + 'PhabricatorConduitResultInterface', ), 'DrydockResourceActivationFailureLogType' => 'DrydockLogType', 'DrydockResourceActivationYieldLogType' => 'DrydockLogType', @@ -7034,6 +7036,7 @@ phutil_register_library_map(array( 'DrydockResourceQuery' => 'DrydockQuery', 'DrydockResourceReclaimLogType' => 'DrydockLogType', 'DrydockResourceReleaseController' => 'DrydockResourceController', + 'DrydockResourceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'DrydockResourceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockResourceStatus' => 'PhabricatorObjectStatus', 'DrydockResourceUpdateWorker' => 'DrydockWorker', diff --git a/src/applications/drydock/conduit/DrydockResourceSearchConduitAPIMethod.php b/src/applications/drydock/conduit/DrydockResourceSearchConduitAPIMethod.php new file mode 100644 index 0000000000..03363b0542 --- /dev/null +++ b/src/applications/drydock/conduit/DrydockResourceSearchConduitAPIMethod.php @@ -0,0 +1,18 @@ +withOwnerPHIDs($map['ownerPHIDs']); } + if ($map['resourcePHIDs']) { + $query->withResourcePHIDs($map['resourcePHIDs']); + } + return $query; } @@ -58,6 +62,11 @@ final class DrydockLeaseSearchEngine ->setKey('ownerPHIDs') ->setAliases(array('owner', 'owners', 'ownerPHID')) ->setDescription(pht('Search leases by owner.')), + id(new PhabricatorPHIDsSearchField()) + ->setLabel(pht('Resources')) + ->setKey('resourcePHIDs') + ->setAliases(array('resorucePHID', 'resource', 'resources')) + ->setDescription(pht('Search leases by resource.')), ); } diff --git a/src/applications/drydock/query/DrydockResourceQuery.php b/src/applications/drydock/query/DrydockResourceQuery.php index c477da20b5..bcbff03663 100644 --- a/src/applications/drydock/query/DrydockResourceQuery.php +++ b/src/applications/drydock/query/DrydockResourceQuery.php @@ -100,46 +100,50 @@ final class DrydockResourceQuery extends DrydockQuery { if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'resource.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'resource.phid IN (%Ls)', $this->phids); } if ($this->types !== null) { $where[] = qsprintf( $conn, - 'type IN (%Ls)', + 'resource.type IN (%Ls)', $this->types); } if ($this->statuses !== null) { $where[] = qsprintf( $conn, - 'status IN (%Ls)', + 'resource.status IN (%Ls)', $this->statuses); } if ($this->blueprintPHIDs !== null) { $where[] = qsprintf( $conn, - 'blueprintPHID IN (%Ls)', + 'resource.blueprintPHID IN (%Ls)', $this->blueprintPHIDs); } if ($this->datasourceQuery !== null) { $where[] = qsprintf( $conn, - 'name LIKE %>', + 'resource.name LIKE %>', $this->datasourceQuery); } return $where; } + protected function getPrimaryTableAlias() { + return 'resource'; + } + } diff --git a/src/applications/drydock/query/DrydockResourceSearchEngine.php b/src/applications/drydock/query/DrydockResourceSearchEngine.php index 8f72fdf217..9015f92408 100644 --- a/src/applications/drydock/query/DrydockResourceSearchEngine.php +++ b/src/applications/drydock/query/DrydockResourceSearchEngine.php @@ -40,6 +40,10 @@ final class DrydockResourceSearchEngine $query->withStatuses($map['statuses']); } + if ($map['blueprintPHIDs']) { + $query->withBlueprintPHIDs($map['blueprintPHIDs']); + } + return $query; } @@ -49,6 +53,12 @@ final class DrydockResourceSearchEngine ->setLabel(pht('Statuses')) ->setKey('statuses') ->setOptions(DrydockResourceStatus::getStatusMap()), + id(new PhabricatorPHIDsSearchField()) + ->setLabel(pht('Blueprints')) + ->setKey('blueprintPHIDs') + ->setAliases(array('blueprintPHID', 'blueprints', 'blueprint')) + ->setDescription( + pht('Search for resources generated by particular blueprints.')), ); } diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index 8ec63cb097..bc672dba3c 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -1,7 +1,9 @@ setKey('blueprintPHID') + ->setType('phid') + ->setDescription(pht('The blueprint which generated this resource.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('status') + ->setType('map') + ->setDescription(pht('Information about resource status.')), + ); + } + + public function getFieldValuesForConduit() { + $status = $this->getStatus(); + + return array( + 'blueprintPHID' => $this->getBlueprintPHID(), + 'status' => array( + 'value' => $status, + 'name' => DrydockResourceStatus::getNameForStatus($status), + ), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } From fc34554892ec142d28b9e261dc529057773301e6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 20 Aug 2019 13:50:03 -0700 Subject: [PATCH 03/27] Replace "bin/people profileimage" with "bin/user enable|empower" Summary: Ref T13382. - Remove "bin/people profileimage" which previously generated profile image caches but now feels obsolete. - Replace it with "bin/user", with "enable" and "empower" flows. This command is now focused on regaining access to an install after you lock your keys inside. - Document the various ways to unlock objects and accounts from the CLI. Test Plan: - Ran `bin/user enable` and `bin/user empower` with various flags. - Grepped for `people profileimage` and found no references. - Grepped for `bin/people` and found no references. - Read documentation. Maniphest Tasks: T13382 Differential Revision: https://secure.phabricator.com/D20724 --- bin/people | 1 - bin/user | 1 + .../manage_user.php} | 4 +- src/__phutil_library_map__.php | 6 +- ...ricatorPeopleManagementEmpowerWorkflow.php | 44 +++++++ ...bricatorPeopleManagementEnableWorkflow.php | 44 +++++++ .../PhabricatorPeopleManagementWorkflow.php | 76 +++++++----- .../PhabricatorPeopleProfileImageWorkflow.php | 85 ------------- src/docs/user/userguide/unlocking.diviner | 116 ++++++++++++++++++ 9 files changed, 254 insertions(+), 123 deletions(-) delete mode 120000 bin/people create mode 120000 bin/user rename scripts/{people/manage_people.php => setup/manage_user.php} (83%) create mode 100644 src/applications/people/management/PhabricatorPeopleManagementEmpowerWorkflow.php create mode 100644 src/applications/people/management/PhabricatorPeopleManagementEnableWorkflow.php delete mode 100644 src/applications/people/management/PhabricatorPeopleProfileImageWorkflow.php create mode 100644 src/docs/user/userguide/unlocking.diviner diff --git a/bin/people b/bin/people deleted file mode 120000 index 985dc4849a..0000000000 --- a/bin/people +++ /dev/null @@ -1 +0,0 @@ -../scripts/people/manage_people.php \ No newline at end of file diff --git a/bin/user b/bin/user new file mode 120000 index 0000000000..4b4b6b7ab5 --- /dev/null +++ b/bin/user @@ -0,0 +1 @@ +../scripts/setup/manage_user.php \ No newline at end of file diff --git a/scripts/people/manage_people.php b/scripts/setup/manage_user.php similarity index 83% rename from scripts/people/manage_people.php rename to scripts/setup/manage_user.php index aeada86da0..f571cb9346 100755 --- a/scripts/people/manage_people.php +++ b/scripts/setup/manage_user.php @@ -6,8 +6,8 @@ require_once $root.'/scripts/__init_script__.php'; $args = new PhutilArgumentParser($argv); $args->setSynopsis(<< 'applications/people/mail/PhabricatorPeopleMailEngine.php', 'PhabricatorPeopleMailEngineException' => 'applications/people/mail/PhabricatorPeopleMailEngineException.php', 'PhabricatorPeopleManageProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleManageProfileMenuItem.php', + 'PhabricatorPeopleManagementEmpowerWorkflow' => 'applications/people/management/PhabricatorPeopleManagementEmpowerWorkflow.php', + 'PhabricatorPeopleManagementEnableWorkflow' => 'applications/people/management/PhabricatorPeopleManagementEnableWorkflow.php', 'PhabricatorPeopleManagementWorkflow' => 'applications/people/management/PhabricatorPeopleManagementWorkflow.php', 'PhabricatorPeopleNewController' => 'applications/people/controller/PhabricatorPeopleNewController.php', 'PhabricatorPeopleNoOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleNoOwnerDatasource.php', @@ -4060,7 +4062,6 @@ phutil_register_library_map(array( 'PhabricatorPeopleProfileCommitsController' => 'applications/people/controller/PhabricatorPeopleProfileCommitsController.php', 'PhabricatorPeopleProfileController' => 'applications/people/controller/PhabricatorPeopleProfileController.php', 'PhabricatorPeopleProfileEditController' => 'applications/people/controller/PhabricatorPeopleProfileEditController.php', - 'PhabricatorPeopleProfileImageWorkflow' => 'applications/people/management/PhabricatorPeopleProfileImageWorkflow.php', 'PhabricatorPeopleProfileManageController' => 'applications/people/controller/PhabricatorPeopleProfileManageController.php', 'PhabricatorPeopleProfileMenuEngine' => 'applications/people/engine/PhabricatorPeopleProfileMenuEngine.php', 'PhabricatorPeopleProfilePictureController' => 'applications/people/controller/PhabricatorPeopleProfilePictureController.php', @@ -10332,6 +10333,8 @@ phutil_register_library_map(array( 'PhabricatorPeopleMailEngine' => 'Phobject', 'PhabricatorPeopleMailEngineException' => 'Exception', 'PhabricatorPeopleManageProfileMenuItem' => 'PhabricatorProfileMenuItem', + 'PhabricatorPeopleManagementEmpowerWorkflow' => 'PhabricatorPeopleManagementWorkflow', + 'PhabricatorPeopleManagementEnableWorkflow' => 'PhabricatorPeopleManagementWorkflow', 'PhabricatorPeopleManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorPeopleNewController' => 'PhabricatorPeopleController', 'PhabricatorPeopleNoOwnerDatasource' => 'PhabricatorTypeaheadDatasource', @@ -10341,7 +10344,6 @@ phutil_register_library_map(array( 'PhabricatorPeopleProfileCommitsController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfileEditController' => 'PhabricatorPeopleProfileController', - 'PhabricatorPeopleProfileImageWorkflow' => 'PhabricatorPeopleManagementWorkflow', 'PhabricatorPeopleProfileManageController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileMenuEngine' => 'PhabricatorProfileMenuEngine', 'PhabricatorPeopleProfilePictureController' => 'PhabricatorPeopleProfileController', diff --git a/src/applications/people/management/PhabricatorPeopleManagementEmpowerWorkflow.php b/src/applications/people/management/PhabricatorPeopleManagementEmpowerWorkflow.php new file mode 100644 index 0000000000..0393a96224 --- /dev/null +++ b/src/applications/people/management/PhabricatorPeopleManagementEmpowerWorkflow.php @@ -0,0 +1,44 @@ +getUserSelectionArguments(), + array()); + + $this + ->setName('empower') + ->setExamples('**empower** --user __username__') + ->setSynopsis(pht('Turn a user account into an administrator account.')) + ->setArguments($arguments); + } + + public function execute(PhutilArgumentParser $args) { + $user = $this->selectUser($args); + $display_name = $user->getUsername(); + + if ($user->getIsAdmin()) { + throw new PhutilArgumentUsageException( + pht( + 'User account "%s" is already an administrator. You can only '. + 'empower accounts that are not yet administrators.', + $display_name)); + } + + $xactions = array(); + $xactions[] = $user->getApplicationTransactionTemplate() + ->setTransactionType(PhabricatorUserEmpowerTransaction::TRANSACTIONTYPE) + ->setNewValue(true); + + $this->applyTransactions($user, $xactions); + + $this->logOkay( + pht('DONE'), + pht('Empowered user account "%s".', $display_name)); + + return 0; + } + +} diff --git a/src/applications/people/management/PhabricatorPeopleManagementEnableWorkflow.php b/src/applications/people/management/PhabricatorPeopleManagementEnableWorkflow.php new file mode 100644 index 0000000000..b721bf221e --- /dev/null +++ b/src/applications/people/management/PhabricatorPeopleManagementEnableWorkflow.php @@ -0,0 +1,44 @@ +getUserSelectionArguments(), + array()); + + $this + ->setName('enable') + ->setExamples('**enable** --user __username__') + ->setSynopsis(pht('Enable a disabled user account.')) + ->setArguments($arguments); + } + + public function execute(PhutilArgumentParser $args) { + $user = $this->selectUser($args); + $display_name = $user->getUsername(); + + if (!$user->getIsDisabled()) { + throw new PhutilArgumentUsageException( + pht( + 'User account "%s" is not disabled. You can only enable accounts '. + 'that are disabled.', + $display_name)); + } + + $xactions = array(); + $xactions[] = $user->getApplicationTransactionTemplate() + ->setTransactionType(PhabricatorUserDisableTransaction::TRANSACTIONTYPE) + ->setNewValue(false); + + $this->applyTransactions($user, $xactions); + + $this->logOkay( + pht('DONE'), + pht('Enabled user account "%s".', $display_name)); + + return 0; + } + +} diff --git a/src/applications/people/management/PhabricatorPeopleManagementWorkflow.php b/src/applications/people/management/PhabricatorPeopleManagementWorkflow.php index 67b474f7e2..d504b8c11b 100644 --- a/src/applications/people/management/PhabricatorPeopleManagementWorkflow.php +++ b/src/applications/people/management/PhabricatorPeopleManagementWorkflow.php @@ -3,45 +3,55 @@ abstract class PhabricatorPeopleManagementWorkflow extends PhabricatorManagementWorkflow { - protected function buildIterator(PhutilArgumentParser $args) { - $usernames = $args->getArg('users'); - - if ($args->getArg('all')) { - if ($usernames) { - throw new PhutilArgumentUsageException( - pht( - 'Specify either a list of users or `%s`, but not both.', - '--all')); - } - return new LiskMigrationIterator(new PhabricatorUser()); - } - - if ($usernames) { - return $this->loadUsersWithUsernames($usernames); - } - - return null; + final protected function getUserSelectionArguments() { + return array( + array( + 'name' => 'user', + 'param' => 'username', + 'help' => pht('User account to act on.'), + ), + ); } - protected function loadUsersWithUsernames(array $usernames) { - $users = array(); - foreach($usernames as $username) { - $query = id(new PhabricatorPeopleQuery()) - ->setViewer($this->getViewer()) - ->withUsernames(array($username)) - ->executeOne(); + final protected function selectUser(PhutilArgumentParser $argv) { + $username = $argv->getArg('user'); - if (!$query) { - throw new PhutilArgumentUsageException( - pht( - '"%s" is not a valid username.', - $username)); - } - $users[] = $query; + if (!strlen($username)) { + throw new PhutilArgumentUsageException( + pht( + 'Select a user account to act on with "--user ".')); } - return $users; + $user = id(new PhabricatorPeopleQuery()) + ->setViewer($this->getViewer()) + ->withUsernames(array($username)) + ->executeOne(); + if (!$user) { + throw new PhutilArgumentUsageException( + pht( + 'No user with username "%s" exists.', + $username)); + } + + return $user; } + final protected function applyTransactions( + PhabricatorUser $user, + array $xactions) { + assert_instances_of($xactions, 'PhabricatorUserTransaction'); + + $viewer = $this->getViewer(); + $application = id(new PhabricatorPeopleApplication())->getPHID(); + $content_source = $this->newContentSource(); + + $editor = $user->getApplicationTransactionEditor() + ->setActor($viewer) + ->setActingAsPHID($application) + ->setContentSource($content_source) + ->setContinueOnMissingFields(true); + + return $editor->applyTransactions($user, $xactions); + } } diff --git a/src/applications/people/management/PhabricatorPeopleProfileImageWorkflow.php b/src/applications/people/management/PhabricatorPeopleProfileImageWorkflow.php deleted file mode 100644 index 8bf3c8e118..0000000000 --- a/src/applications/people/management/PhabricatorPeopleProfileImageWorkflow.php +++ /dev/null @@ -1,85 +0,0 @@ -setName('profileimage') - ->setExamples('**profileimage** --users __username__') - ->setSynopsis(pht('Generate default profile images.')) - ->setArguments( - array( - array( - 'name' => 'all', - 'help' => pht( - 'Generate default profile images for all users.'), - ), - array( - 'name' => 'force', - 'short' => 'f', - 'help' => pht( - 'Force a default profile image to be replaced.'), - ), - array( - 'name' => 'users', - 'wildcard' => true, - ), - )); - } - - public function execute(PhutilArgumentParser $args) { - $console = PhutilConsole::getConsole(); - - $is_force = $args->getArg('force'); - $is_all = $args->getArg('all'); - - $gd = function_exists('imagecreatefromstring'); - if (!$gd) { - throw new PhutilArgumentUsageException( - pht( - 'GD is not installed for php-cli. Aborting.')); - } - - $iterator = $this->buildIterator($args); - if (!$iterator) { - throw new PhutilArgumentUsageException( - pht( - 'Either specify a list of users to update, or use `%s` '. - 'to update all users.', - '--all')); - } - - $version = PhabricatorFilesComposeAvatarBuiltinFile::VERSION; - $generator = new PhabricatorFilesComposeAvatarBuiltinFile(); - - foreach ($iterator as $user) { - $username = $user->getUsername(); - $default_phid = $user->getDefaultProfileImagePHID(); - $gen_version = $user->getDefaultProfileImageVersion(); - - $generate = false; - if ($gen_version != $version) { - $generate = true; - } - - if ($default_phid == null || $is_force || $generate) { - $console->writeOut( - "%s\n", - pht( - 'Generating profile image for "%s".', - $username)); - - $generator->updateUser($user); - } else { - $console->writeOut( - "%s\n", - pht( - 'Default profile image "%s" already set for "%s".', - $version, - $username)); - } - } - } - -} diff --git a/src/docs/user/userguide/unlocking.diviner b/src/docs/user/userguide/unlocking.diviner new file mode 100644 index 0000000000..7dc29f69bd --- /dev/null +++ b/src/docs/user/userguide/unlocking.diviner @@ -0,0 +1,116 @@ +@title User Guide: Unlocking Objects +@group userguide + +Explains how to access locked or invisible objects and accounts. + +Overview +======== + +Phabricator tries to make it difficult for users to lock themselves out of +things, but you can occasionally end up in situations where no one has access +to an object that you need access to. + +For example, sometimes the only user who had edit permission for something has +left the organization, or you configured a "Phase of the Moon" policy rule and +the stars aren't currently aligned. + +You can use various CLI tools to unlock objects and accounts if you need to +regain access. + + +Unlocking Accounts +================== + +If you need to regain access to an object, the easiest approach is usually to +recover access to the account which owns it, then change the object policies +to be more open using the web UI. + +For example, if an important task was accidentally locked so that only a user +who is currently on vacation can edit it, you can log in as that user and +change the edit policy to something more permissive. + +To regain access to an account: + +``` +$ ./bin/auth recover +``` + +If the account you're recovering access to has MFA or other session prompts, +use the `--force-full-session` to bypass them: + +``` +$ ./bin/auth recover --force-full-session +``` + +In either case, the command will give you a link you a one-time link you can +use to access the account from the web UI. From there, you can open up objects +or change settings. + + +Unlocking MFA +============= + +You can completely strip MFA from a user account with: + +``` +$ ./bin/auth strip --user ... +``` + +For detailed help on managing and stripping MFA, see the instructions in +@{article:User Guide: Multi-Factor Authentication} + + +Unlocking Objects +================= + +If you aren't sure who owns an object, or no user account has access to an +object, you can directly change object policies from the CLI: + +``` +$ ./bin/policy unlock [--view ...] [--edit ...] [--owner ...] +``` + +To identify the object you want to unlock, you can specify an object name (like +`T123`) or a PHID as the `` parameter. + +Use the `--view` and `--edit` flags (and, for some objects, the `--owner` +flag) to specify new policies for the object. + +For example, to make task `T123` editable by user `@alice`, run: + +``` +$ ./bin/policy unlock T123 --edit alice +``` + +Not every object has mutable view and edit policies, and not every object has +an owner, so each flag only works on some types of objects. + +From here, you can log in to the web UI and change the relevant policies to +whatever you want to set them to. + + +No Enabled Users +================ + +If you accidentally disabled all administrator accounts, you can enable a +disabled account from the CLI like this: + +``` +$ ./bin/user enable --user +``` + +From here, recover the account or log in normally. + + +No Administrators +================= + +If you accidentally deleted all the administrator accounts, you can empower +a user as an administrator from the CLI like this: + +``` +$ ./bin/user empower --user +``` + +This will upgrade the user account from a regular account to an administrator +account. From 64b399d9be2684c4dad39fe8bab482434cd9fcf1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 20 Aug 2019 17:41:33 -0700 Subject: [PATCH 04/27] Remove "bin/accountadmin" and "scripts/user/add_user.php" Summary: Fixes T13382. Depends on D20724. These ancient scripts are no longer necessary since we've had a smooth web-based onboarding process for a long time. I retained `bin/user empower` and `bin/user enable` for recovering from situations where you accidentally delete or disable all administrators. This is normally difficult, but some users are industrious. Test Plan: Grepped for `accountadmin` and `add_user.php`, found no more hits. Maniphest Tasks: T13382 Differential Revision: https://secure.phabricator.com/D20725 --- bin/accountadmin | 1 - scripts/user/account_admin.php | 228 ------------------ scripts/user/add_user.php | 73 ------ ...figuring_accounts_and_registration.diviner | 26 +- 4 files changed, 2 insertions(+), 326 deletions(-) delete mode 120000 bin/accountadmin delete mode 100755 scripts/user/account_admin.php delete mode 100755 scripts/user/add_user.php diff --git a/bin/accountadmin b/bin/accountadmin deleted file mode 120000 index a846766c26..0000000000 --- a/bin/accountadmin +++ /dev/null @@ -1 +0,0 @@ -../scripts/user/account_admin.php \ No newline at end of file diff --git a/scripts/user/account_admin.php b/scripts/user/account_admin.php deleted file mode 100755 index 4e4500a2f7..0000000000 --- a/scripts/user/account_admin.php +++ /dev/null @@ -1,228 +0,0 @@ -#!/usr/bin/env php -establishConnection('r'), - 'SELECT * FROM %T LIMIT 1', - $table->getTableName()); -$is_first_user = (!$any_user); - -if ($is_first_user) { - echo pht( - "WARNING\n\n". - "You're about to create the first account on this install. Normally, ". - "you should use the web interface to create the first account, not ". - "this script.\n\n". - "If you use the web interface, it will drop you into a nice UI workflow ". - "which gives you more help setting up your install. If you create an ". - "account with this script instead, you will skip the setup help and you ". - "will not be able to access it later."); - if (!phutil_console_confirm(pht('Skip easy setup and create account?'))) { - echo pht('Cancelled.')."\n"; - exit(1); - } -} - -echo pht( - 'Enter a username to create a new account or edit an existing account.'); - -$username = phutil_console_prompt(pht('Enter a username:')); -if (!strlen($username)) { - echo pht('Cancelled.')."\n"; - exit(1); -} - -if (!PhabricatorUser::validateUsername($username)) { - $valid = PhabricatorUser::describeValidUsername(); - echo pht("The username '%s' is invalid. %s", $username, $valid)."\n"; - exit(1); -} - - -$user = id(new PhabricatorUser())->loadOneWhere( - 'username = %s', - $username); - -if (!$user) { - $original = new PhabricatorUser(); - - echo pht("There is no existing user account '%s'.", $username)."\n"; - $ok = phutil_console_confirm( - pht("Do you want to create a new '%s' account?", $username), - $default_no = false); - if (!$ok) { - echo pht('Cancelled.')."\n"; - exit(1); - } - $user = new PhabricatorUser(); - $user->setUsername($username); - - $is_new = true; -} else { - $original = clone $user; - - echo pht("There is an existing user account '%s'.", $username)."\n"; - $ok = phutil_console_confirm( - pht("Do you want to edit the existing '%s' account?", $username), - $default_no = false); - if (!$ok) { - echo pht('Cancelled.')."\n"; - exit(1); - } - - $is_new = false; -} - -$user_realname = $user->getRealName(); -if (strlen($user_realname)) { - $realname_prompt = ' ['.$user_realname.']:'; -} else { - $realname_prompt = ':'; -} -$realname = nonempty( - phutil_console_prompt(pht('Enter user real name').$realname_prompt), - $user_realname); -$user->setRealName($realname); - -// When creating a new user we prompt for an email address; when editing an -// existing user we just skip this because it would be quite involved to provide -// a reasonable CLI interface for editing multiple addresses and managing email -// verification and primary addresses. - -$create_email = null; -if ($is_new) { - do { - $email = phutil_console_prompt(pht('Enter user email address:')); - $duplicate = id(new PhabricatorUserEmail())->loadOneWhere( - 'address = %s', - $email); - if ($duplicate) { - echo pht( - "ERROR: There is already a user with that email address. ". - "Each user must have a unique email address.\n"); - } else { - break; - } - } while (true); - - $create_email = $email; -} - -$is_system_agent = $user->getIsSystemAgent(); -$set_system_agent = phutil_console_confirm( - pht('Is this user a bot?'), - $default_no = !$is_system_agent); - -$verify_email = null; -$set_verified = false; -// Allow administrators to verify primary email addresses at this time in edit -// scenarios. (Create will work just fine from here as we auto-verify email -// on create.) -if (!$is_new) { - $verify_email = $user->loadPrimaryEmail(); - if (!$verify_email->getIsVerified()) { - $set_verified = phutil_console_confirm( - pht('Should the primary email address be verified?'), - $default_no = true); - } else { - // Already verified so let's not make a fuss. - $verify_email = null; - } -} - -$is_admin = $user->getIsAdmin(); -$set_admin = phutil_console_confirm( - pht('Should this user be an administrator?'), - $default_no = !$is_admin); - -echo "\n\n".pht('ACCOUNT SUMMARY')."\n\n"; -$tpl = "%12s %-30s %-30s\n"; -printf($tpl, null, pht('OLD VALUE'), pht('NEW VALUE')); -printf($tpl, pht('Username'), $original->getUsername(), $user->getUsername()); -printf($tpl, pht('Real Name'), $original->getRealName(), $user->getRealName()); -if ($is_new) { - printf($tpl, pht('Email'), '', $create_email); -} - -printf( - $tpl, - pht('Bot'), - $original->getIsSystemAgent() ? 'Y' : 'N', - $set_system_agent ? 'Y' : 'N'); - -if ($verify_email) { - printf( - $tpl, - pht('Verify Email'), - $verify_email->getIsVerified() ? 'Y' : 'N', - $set_verified ? 'Y' : 'N'); -} - -printf( - $tpl, - pht('Admin'), - $original->getIsAdmin() ? 'Y' : 'N', - $set_admin ? 'Y' : 'N'); - -echo "\n"; - -if (!phutil_console_confirm(pht('Save these changes?'), $default_no = false)) { - echo pht('Cancelled.')."\n"; - exit(1); -} - -$user->openTransaction(); - - $editor = new PhabricatorUserEditor(); - - // TODO: This is wrong, but we have a chicken-and-egg problem when you use - // this script to create the first user. - $editor->setActor($user); - - if ($is_new) { - $email = id(new PhabricatorUserEmail()) - ->setAddress($create_email) - ->setIsVerified(1); - - // Unconditionally approve new accounts created from the CLI. - $user->setIsApproved(1); - - $editor->createNewUser($user, $email); - } else { - if ($verify_email) { - $user->setIsEmailVerified(1); - $verify_email->setIsVerified($set_verified ? 1 : 0); - } - $editor->updateUser($user, $verify_email); - } - - $editor->makeSystemAgentUser($user, $set_system_agent); - - $xactions = array(); - $xactions[] = id(new PhabricatorUserTransaction()) - ->setTransactionType( - PhabricatorUserEmpowerTransaction::TRANSACTIONTYPE) - ->setNewValue($set_admin); - - $actor = PhabricatorUser::getOmnipotentUser(); - $content_source = PhabricatorContentSource::newForSource( - PhabricatorConsoleContentSource::SOURCECONST); - - $people_application_phid = id(new PhabricatorPeopleApplication())->getPHID(); - - $transaction_editor = id(new PhabricatorUserTransactionEditor()) - ->setActor($actor) - ->setActingAsPHID($people_application_phid) - ->setContentSource($content_source) - ->setContinueOnNoEffect(true) - ->setContinueOnMissingFields(true); - - $transaction_editor->applyTransactions($user, $xactions); - -$user->saveTransaction(); - -echo pht('Saved changes.')."\n"; diff --git a/scripts/user/add_user.php b/scripts/user/add_user.php deleted file mode 100755 index 2554ab3ddc..0000000000 --- a/scripts/user/add_user.php +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env php - '); - exit(1); -} - -$username = $argv[1]; -$email = $argv[2]; -$realname = $argv[3]; -$admin = $argv[4]; - -$admin = id(new PhabricatorUser())->loadOneWhere( - 'username = %s', - $argv[4]); -if (!$admin) { - throw new Exception( - pht( - 'Admin user must be the username of a valid Phabricator account, used '. - 'to send the new user a welcome email.')); -} - -$existing_user = id(new PhabricatorUser())->loadOneWhere( - 'username = %s', - $username); -if ($existing_user) { - throw new Exception( - pht( - "There is already a user with the username '%s'!", - $username)); -} - -$existing_email = id(new PhabricatorUserEmail())->loadOneWhere( - 'address = %s', - $email); -if ($existing_email) { - throw new Exception( - pht( - "There is already a user with the email '%s'!", - $email)); -} - -$user = new PhabricatorUser(); -$user->setUsername($username); -$user->setRealname($realname); -$user->setIsApproved(1); - -$email_object = id(new PhabricatorUserEmail()) - ->setAddress($email) - ->setIsVerified(1); - -id(new PhabricatorUserEditor()) - ->setActor($admin) - ->createNewUser($user, $email_object); - -$welcome_engine = id(new PhabricatorPeopleWelcomeMailEngine()) - ->setSender($admin) - ->setRecipient($user); -if ($welcome_engine->canSendMail()) { - $welcome_engine->sendMail(); -} - -echo pht( - "Created user '%s' (realname='%s', email='%s').\n", - $username, - $realname, - $email); diff --git a/src/docs/user/configuration/configuring_accounts_and_registration.diviner b/src/docs/user/configuration/configuring_accounts_and_registration.diviner index a56d7377cb..e703c46402 100644 --- a/src/docs/user/configuration/configuring_accounts_and_registration.diviner +++ b/src/docs/user/configuration/configuring_accounts_and_registration.diviner @@ -49,30 +49,8 @@ phabricator/ $ ./bin/auth recover ...where `` is the account username you want to recover access to. This will generate a link which will log you in as the specified user. - -Managing Accounts with the Web Console -====================================== - -To manage accounts from the web, login as an administrator account and go to -`/people/` or click "People" on the homepage. Provided you're an admin, -you'll see options to create or edit accounts. - - -Manually Creating New Accounts -============================== - -There are two ways to manually create new accounts: via the web UI using -the "People" application (this is easiest), or via the CLI using the -`accountadmin` binary (this has a few more options). - -To use the CLI script, run: - - phabricator/ $ ./bin/accountadmin - -Some options (like changing certain account flags) are only available from -the CLI. You can also use this script to make a user -an administrator (if you accidentally remove your admin flag) or to create an -administrative account. +For more details on recovering access to accounts and unlocking objects, see +@{article:User Guide: Unlocking Objects}. Next Steps From c4399313737bac4a8f9dda1003ae41ce2432a393 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 21 Aug 2019 17:50:38 -0700 Subject: [PATCH 05/27] Respect "disabled" custom field status granted by "subtype" configuration in form validation Summary: Fixes T13384. Currently, the subtype "disabled" configuration is not respected when selecting fields for `ROLE_EDIT`. The only meaningful caller for `ROLE_EDIT` is transaction validation, but transaction validation should respect fields being disabled by subtype configuration. Test Plan: - Added a "required" Maniphest custom field "F", then "disabled" it in a subtype "S". - Created a task of subtype "S". - Before: Form submission fails with error "F is required", even though the field is not actually visible on the form and can not be set. - After: Form submits cleanly and creates the task. Maniphest Tasks: T13384 Differential Revision: https://secure.phabricator.com/D20726 --- src/infrastructure/customfield/field/PhabricatorCustomField.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php index c6c70a9614..9e2bf6895f 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomField.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php @@ -1648,6 +1648,7 @@ abstract class PhabricatorCustomField extends Phobject { $subtype_roles = array( self::ROLE_EDITENGINE, self::ROLE_VIEW, + self::ROLE_EDIT, ); $subtype_roles = array_fuse($subtype_roles); From 5741514aeb5b1eb4b722159154f682a1627463b0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 21 Aug 2019 19:11:19 -0700 Subject: [PATCH 06/27] When a client submits an overlong "sourcePath", truncate it and continue Summary: Ref T13385. Currently, if you run `arc diff` in a CWD with more than 255 characters, the workflow fatals against the length of the `sourcePath` database column. In the long term, removing this property is likely desirable. For now, truncate long values and continue. This only meaningfully impacts relatively obscure interactive SVN workflows negatively, and even there, "some arc commands are glitchy in very long working directories in SVN" is still better than "arc diff fatals". Test Plan: - Modified `arc` to submit very long source paths. - Ran `arc diff`. - Before: Fatal when inserting >255 characters into `sourcePath`. - After: Path truncated at 255 bytes. Maniphest Tasks: T13385 Differential Revision: https://secure.phabricator.com/D20727 --- ...DifferentialCreateDiffConduitAPIMethod.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php index 8a0da78865..b0582f8f67 100644 --- a/src/applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php @@ -119,8 +119,11 @@ final class DifferentialCreateDiffConduitAPIMethod break; } + $source_path = $request->getValue('sourcePath'); + $source_path = $this->normalizeSourcePath($source_path); + $diff_data_dict = array( - 'sourcePath' => $request->getValue('sourcePath'), + 'sourcePath' => $source_path, 'sourceMachine' => $request->getValue('sourceMachine'), 'branch' => $request->getValue('branch'), 'creationMethod' => $request->getValue('creationMethod'), @@ -158,4 +161,18 @@ final class DifferentialCreateDiffConduitAPIMethod ); } + private function normalizeSourcePath($source_path) { + // See T13385. This property is probably headed for deletion. Until we get + // there, stop errors arising from running "arc diff" in a working copy + // with too many characters. + + $max_size = id(new DifferentialDiff()) + ->getColumnMaximumByteLength('sourcePath'); + + return id(new PhutilUTF8StringTruncator()) + ->setMaximumBytes($max_size) + ->setTerminator('') + ->truncateString($source_path); + } + } From f1b054a20fd5f1de02a3aaabf0a7cb0a98f11715 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 21 Aug 2019 19:54:24 -0700 Subject: [PATCH 07/27] Correct the interaction between overheating and offset-based paging Summary: Ref T13386. If you issue `differential.query` with a large offset (like 3000), it can overheat regardless of policy filtering and fail with a nonsensical error message. This is because the overheating limit is based only on the query limit, not on the offset. For example, querying for "limit = 100" will never examine more than 1,100 rows, so a query with "limit = 100, offset = 3000" will always fail (provided there are at least that many revisions). Not all numbers work like you might expect them to becuase there's also a 1024-row fetch window, but basically small limits plus big offsets always fail. Test Plan: Artificially reduced the internal window size from 1024 to 5, then ran `differential.query` with `offset=50` and `limit=3`. Before: overheated with weird error message. After: clean result. Maniphest Tasks: T13386 Differential Revision: https://secure.phabricator.com/D20728 --- .../query/policy/PhabricatorPolicyAwareQuery.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php index 8780584f94..a770c326f9 100644 --- a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php @@ -233,7 +233,10 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { // number of records when the viewer can see few or none of them. See // T11773 for some discussion. $this->isOverheated = false; - $overheat_limit = $limit * 10; + + // See T13386. If we on an old offset-based paging workflow, we need + // to base the overheating limit on both the offset and limit. + $overheat_limit = $need * 10; $total_seen = 0; do { From 109d7dcaf197b8b3bdd95f51f46d16e61c0d685b Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 22 Aug 2019 07:05:38 -0700 Subject: [PATCH 08/27] Convert "Empower" from state-based MFA to one-shot MFA Summary: Ref T13382. Currently, the "Make Administrator" action in the web UI does state-based MFA. Convert it to one-shot MFA. Test Plan: Empowered and unempowered a user from the web UI, got one-shot MFA'd. Empowered a user from the CLI, no MFA issues. Maniphest Tasks: T13382 Differential Revision: https://secure.phabricator.com/D20729 --- .../controller/PhabricatorPeopleEmpowerController.php | 11 +++-------- .../xaction/PhabricatorUserEmpowerTransaction.php | 7 +++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/applications/people/controller/PhabricatorPeopleEmpowerController.php b/src/applications/people/controller/PhabricatorPeopleEmpowerController.php index 09021bf73e..22e7c22b68 100644 --- a/src/applications/people/controller/PhabricatorPeopleEmpowerController.php +++ b/src/applications/people/controller/PhabricatorPeopleEmpowerController.php @@ -17,14 +17,8 @@ final class PhabricatorPeopleEmpowerController $done_uri = $this->getApplicationURI("manage/{$id}/"); - id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( - $viewer, - $request, - $done_uri); - $validation_exception = null; - - if ($request->isFormPost()) { + if ($request->isFormOrHisecPost()) { $xactions = array(); $xactions[] = id(new PhabricatorUserTransaction()) ->setTransactionType( @@ -34,7 +28,8 @@ final class PhabricatorPeopleEmpowerController $editor = id(new PhabricatorUserTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) - ->setContinueOnMissingFields(true); + ->setContinueOnMissingFields(true) + ->setCancelURI($done_uri); try { $editor->applyTransactions($user, $xactions); diff --git a/src/applications/people/xaction/PhabricatorUserEmpowerTransaction.php b/src/applications/people/xaction/PhabricatorUserEmpowerTransaction.php index 5499f5d8cb..d17418636f 100644 --- a/src/applications/people/xaction/PhabricatorUserEmpowerTransaction.php +++ b/src/applications/people/xaction/PhabricatorUserEmpowerTransaction.php @@ -86,4 +86,11 @@ final class PhabricatorUserEmpowerTransaction return null; } + + public function shouldTryMFA( + $object, + PhabricatorApplicationTransaction $xaction) { + return true; + } + } From 353155a2034ed22bf8e5644ddc3eaa70a84e3cd9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 22 Aug 2019 13:04:25 -0700 Subject: [PATCH 09/27] Add "modifiedStart" and "modifiedEnd" constraints to "differential.revision.search" Summary: Fixes T13386. See PHI1391. These constraints largely exist already, but are not yet exposed to Conduit. Also, tweak some keys to support the underlying query. Test Plan: Ran `differential.revision.search` queries with the new constraints. Maniphest Tasks: T13386 Differential Revision: https://secure.phabricator.com/D20730 --- .../query/DifferentialRevisionSearchEngine.php | 18 ++++++++++++++++++ .../storage/DifferentialRevision.php | 8 +++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/applications/differential/query/DifferentialRevisionSearchEngine.php b/src/applications/differential/query/DifferentialRevisionSearchEngine.php index 14c9dd0301..2bf196db0c 100644 --- a/src/applications/differential/query/DifferentialRevisionSearchEngine.php +++ b/src/applications/differential/query/DifferentialRevisionSearchEngine.php @@ -51,6 +51,12 @@ final class DifferentialRevisionSearchEngine $map['createdEnd']); } + if ($map['modifiedStart'] || $map['modifiedEnd']) { + $query->withUpdatedEpochBetween( + $map['modifiedStart'], + $map['modifiedEnd']); + } + return $query; } @@ -100,6 +106,18 @@ final class DifferentialRevisionSearchEngine ->setKey('createdEnd') ->setDescription( pht('Find revisions created at or before a particular time.')), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Modified After')) + ->setKey('modifiedStart') + ->setIsHidden(true) + ->setDescription( + pht('Find revisions modified at or after a particular time.')), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Modified Before')) + ->setKey('modifiedEnd') + ->setIsHidden(true) + ->setDescription( + pht('Find revisions modified at or before a particular time.')), ); } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 5e70b52188..c5eaa71b2d 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -112,11 +112,6 @@ final class DifferentialRevision extends DifferentialDAO 'repositoryPHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( - 'key_phid' => null, - 'phid' => array( - 'columns' => array('phid'), - 'unique' => true, - ), 'authorPHID' => array( 'columns' => array('authorPHID', 'status'), ), @@ -131,6 +126,9 @@ final class DifferentialRevision extends DifferentialDAO 'key_status' => array( 'columns' => array('status', 'phid'), ), + 'key_modified' => array( + 'columns' => array('dateModified'), + ), ), ) + parent::getConfiguration(); } From ecbc82da33c5dcfc4db3e9e6111cfdccc9c3180d Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 22 Aug 2019 13:29:42 -0700 Subject: [PATCH 10/27] Expose "commits.add|set|remove" on "maniphest.edit" API calls Summary: See PHI1396. Ideally this would be some kind of general-purpose tie-in to object relationships, but see D18456 for precedent. Test Plan: Used `maniphest.edit` to edit associated commits for a task. Differential Revision: https://secure.phabricator.com/D20731 --- .../maniphest/editor/ManiphestEditEngine.php | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index 7cb788e199..fc3c48b205 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -261,6 +261,7 @@ EODOCS $parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; $subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; + $commit_type = ManiphestTaskHasCommitEdgeType::EDGECONST; $src_phid = $object->getPHID(); if ($src_phid) { @@ -270,6 +271,7 @@ EODOCS array( $parent_type, $subtask_type, + $commit_type, )); $edge_query->execute(); @@ -280,9 +282,14 @@ EODOCS $subtask_phids = $edge_query->getDestinationPHIDs( array($src_phid), array($subtask_type)); + + $commit_phids = $edge_query->getDestinationPHIDs( + array($src_phid), + array($commit_type)); } else { $parent_phids = array(); $subtask_phids = array(); + $commit_phids = array(); } $fields[] = id(new PhabricatorHandlesEditField()) @@ -307,7 +314,19 @@ EODOCS ->setIsFormField(false) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $subtask_type) - ->setValue($parent_phids); + ->setValue($subtask_phids); + + $fields[] = id(new PhabricatorHandlesEditField()) + ->setKey('commits') + ->setLabel(pht('Commits')) + ->setDescription(pht('Related commits.')) + ->setConduitDescription(pht('Change the related commits for this task.')) + ->setConduitTypeDescription(pht('List of related commit PHIDs.')) + ->setUseEdgeTransactions(true) + ->setIsFormField(false) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $commit_type) + ->setValue($commit_phids); return $fields; } From 719a7d82c51f58f9c89f9160f5b2e1873d4b37a3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Aug 2019 11:29:45 -0700 Subject: [PATCH 11/27] Refactor the Phortune account detail page into a series of smaller, more focused sections Summary: Ref T13366. Some of the information architecture is a little muddy here, notably an item called "Billing / History" which contains payment methods. Split things up a bit to prepare for adding support for "Email Addresses". Test Plan: {F6676988} Maniphest Tasks: T13366 Differential Revision: https://secure.phabricator.com/D20697 --- src/__phutil_library_map__.php | 18 ++- .../PhabricatorPhortuneApplication.php | 22 +-- .../PhortuneAccountAddManagerController.php | 8 +- .../PhortuneAccountChargesController.php | 79 +++++++++++ .../PhortuneAccountDetailsController.php | 132 ++++++++++++++++++ ... => PhortuneAccountManagersController.php} | 6 +- .../PhortuneAccountOrdersController.php | 37 +++++ ... => PhortuneAccountOverviewController.php} | 118 ++-------------- ...ortuneAccountPaymentMethodsController.php} | 59 ++------ .../PhortuneAccountProfileController.php | 93 ++++++++++-- .../editor/PhortuneAccountEditEngine.php | 6 +- .../phortune/storage/PhortuneAccount.php | 6 + 12 files changed, 390 insertions(+), 194 deletions(-) create mode 100644 src/applications/phortune/controller/account/PhortuneAccountChargesController.php create mode 100644 src/applications/phortune/controller/account/PhortuneAccountDetailsController.php rename src/applications/phortune/controller/account/{PhortuneAccountManagerController.php => PhortuneAccountManagersController.php} (93%) create mode 100644 src/applications/phortune/controller/account/PhortuneAccountOrdersController.php rename src/applications/phortune/controller/account/{PhortuneAccountViewController.php => PhortuneAccountOverviewController.php} (51%) rename src/applications/phortune/controller/account/{PhortuneAccountBillingController.php => PhortuneAccountPaymentMethodsController.php} (65%) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6f36855761..100f7fc30a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5221,25 +5221,28 @@ phutil_register_library_map(array( 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', 'PhortuneAccountAddManagerController' => 'applications/phortune/controller/account/PhortuneAccountAddManagerController.php', 'PhortuneAccountBillingAddressTransaction' => 'applications/phortune/xaction/PhortuneAccountBillingAddressTransaction.php', - 'PhortuneAccountBillingController' => 'applications/phortune/controller/account/PhortuneAccountBillingController.php', 'PhortuneAccountBillingNameTransaction' => 'applications/phortune/xaction/PhortuneAccountBillingNameTransaction.php', 'PhortuneAccountChargeListController' => 'applications/phortune/controller/account/PhortuneAccountChargeListController.php', + 'PhortuneAccountChargesController' => 'applications/phortune/controller/account/PhortuneAccountChargesController.php', 'PhortuneAccountController' => 'applications/phortune/controller/account/PhortuneAccountController.php', + 'PhortuneAccountDetailsController' => 'applications/phortune/controller/account/PhortuneAccountDetailsController.php', 'PhortuneAccountEditController' => 'applications/phortune/controller/account/PhortuneAccountEditController.php', 'PhortuneAccountEditEngine' => 'applications/phortune/editor/PhortuneAccountEditEngine.php', 'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php', 'PhortuneAccountHasMemberEdgeType' => 'applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php', 'PhortuneAccountListController' => 'applications/phortune/controller/account/PhortuneAccountListController.php', - 'PhortuneAccountManagerController' => 'applications/phortune/controller/account/PhortuneAccountManagerController.php', + 'PhortuneAccountManagersController' => 'applications/phortune/controller/account/PhortuneAccountManagersController.php', 'PhortuneAccountNameTransaction' => 'applications/phortune/xaction/PhortuneAccountNameTransaction.php', + 'PhortuneAccountOrdersController' => 'applications/phortune/controller/account/PhortuneAccountOrdersController.php', + 'PhortuneAccountOverviewController' => 'applications/phortune/controller/account/PhortuneAccountOverviewController.php', 'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php', + 'PhortuneAccountPaymentMethodsController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php', 'PhortuneAccountProfileController' => 'applications/phortune/controller/account/PhortuneAccountProfileController.php', 'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php', 'PhortuneAccountSubscriptionController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionController.php', 'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php', 'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php', 'PhortuneAccountTransactionType' => 'applications/phortune/xaction/PhortuneAccountTransactionType.php', - 'PhortuneAccountViewController' => 'applications/phortune/controller/account/PhortuneAccountViewController.php', 'PhortuneAdHocCart' => 'applications/phortune/cart/PhortuneAdHocCart.php', 'PhortuneAdHocProduct' => 'applications/phortune/product/PhortuneAdHocProduct.php', 'PhortuneAddPaymentMethodAction' => 'applications/phortune/action/PhortuneAddPaymentMethodAction.php', @@ -11753,25 +11756,28 @@ phutil_register_library_map(array( ), 'PhortuneAccountAddManagerController' => 'PhortuneController', 'PhortuneAccountBillingAddressTransaction' => 'PhortuneAccountTransactionType', - 'PhortuneAccountBillingController' => 'PhortuneAccountProfileController', 'PhortuneAccountBillingNameTransaction' => 'PhortuneAccountTransactionType', 'PhortuneAccountChargeListController' => 'PhortuneController', + 'PhortuneAccountChargesController' => 'PhortuneAccountProfileController', 'PhortuneAccountController' => 'PhortuneController', + 'PhortuneAccountDetailsController' => 'PhortuneAccountProfileController', 'PhortuneAccountEditController' => 'PhortuneController', 'PhortuneAccountEditEngine' => 'PhabricatorEditEngine', 'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneAccountHasMemberEdgeType' => 'PhabricatorEdgeType', 'PhortuneAccountListController' => 'PhortuneController', - 'PhortuneAccountManagerController' => 'PhortuneAccountProfileController', + 'PhortuneAccountManagersController' => 'PhortuneAccountProfileController', 'PhortuneAccountNameTransaction' => 'PhortuneAccountTransactionType', + 'PhortuneAccountOrdersController' => 'PhortuneAccountProfileController', + 'PhortuneAccountOverviewController' => 'PhortuneAccountProfileController', 'PhortuneAccountPHIDType' => 'PhabricatorPHIDType', + 'PhortuneAccountPaymentMethodsController' => 'PhortuneAccountProfileController', 'PhortuneAccountProfileController' => 'PhortuneAccountController', 'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneAccountSubscriptionController' => 'PhortuneAccountProfileController', 'PhortuneAccountTransaction' => 'PhabricatorModularTransaction', 'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortuneAccountTransactionType' => 'PhabricatorModularTransactionType', - 'PhortuneAccountViewController' => 'PhortuneAccountProfileController', 'PhortuneAdHocCart' => 'PhortuneCartImplementation', 'PhortuneAdHocProduct' => 'PhortuneProductImplementation', 'PhortuneAddPaymentMethodAction' => 'PhabricatorSystemAction', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 0ffc138f65..de3c740e2b 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -35,7 +35,7 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { '/phortune/' => array( '' => 'PhortuneLandingController', '(?P\d+)/' => array( - '' => 'PhortuneAccountViewController', + '' => 'PhortuneAccountOverviewController', 'card/' => array( 'new/' => 'PhortunePaymentMethodCreateController', ), @@ -69,15 +69,17 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { '' => 'PhortuneAccountListController', $this->getEditRoutePattern('edit/') => 'PhortuneAccountEditController', - 'edit/(?:(?P\d+)/)?' => 'PhortuneAccountEditController', - 'add/manager/(?:(?P\d+)/)?' - => 'PhortuneAccountAddManagerController', - 'billing/(?:(?P\d+)/)?' => 'PhortuneAccountBillingController', - 'subscription/(?:(?P\d+)/)?' - => 'PhortuneAccountSubscriptionController', - 'manager/' => array( - '(?:(?P\d+)/)?' => 'PhortuneAccountManagerController', - 'add/(?:(?P\d+)/)?' => 'PhortuneAccountAddManagerController', + + '(?P\d+)/' => array( + 'details/' => 'PhortuneAccountDetailsController', + 'methods/' => 'PhortuneAccountPaymentMethodsController', + 'orders/' => 'PhortuneAccountOrdersController', + 'charges/' => 'PhortuneAccountChargesController', + 'subscriptions/' => 'PhortuneAccountSubscriptionController', + 'managers/' => array( + '' => 'PhortuneAccountManagersController', + 'add/' => 'PhortuneAccountAddManagerController', + ), ), ), 'product/' => array( diff --git a/src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php b/src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php index 34bb0a480b..0c8c71d968 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php @@ -4,7 +4,7 @@ final class PhortuneAccountAddManagerController extends PhortuneController { public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $id = $request->getURIData('id'); + $id = $request->getURIData('accountID'); $account = id(new PhortuneAccountQuery()) ->setViewer($viewer) @@ -21,7 +21,7 @@ final class PhortuneAccountAddManagerController extends PhortuneController { $v_managers = array(); $e_managers = null; - $account_uri = $this->getApplicationURI("/account/manager/{$id}/"); + $account_uri = $this->getApplicationURI("/account/{$id}/managers/"); if ($request->isFormPost()) { $xactions = array(); @@ -64,11 +64,11 @@ final class PhortuneAccountAddManagerController extends PhortuneController { ->setError($e_managers)); return $this->newDialog() - ->setTitle(pht('Add New Manager')) + ->setTitle(pht('Add New Managers')) ->appendForm($form) ->setWidth(AphrontDialogView::WIDTH_FORM) ->addCancelButton($account_uri) - ->addSubmitButton(pht('Add Manager')); + ->addSubmitButton(pht('Add Managers')); } diff --git a/src/applications/phortune/controller/account/PhortuneAccountChargesController.php b/src/applications/phortune/controller/account/PhortuneAccountChargesController.php new file mode 100644 index 0000000000..6fc0209f94 --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountChargesController.php @@ -0,0 +1,79 @@ +loadAccount(); + if ($response) { + return $response; + } + + $account = $this->getAccount(); + $title = $account->getName(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Order History')); + + $header = $this->buildHeaderView(); + $charge_history = $this->buildChargeHistorySection($account); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter( + array( + $charge_history, + )); + + $navigation = $this->buildSideNavView('charges'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + } + + private function buildChargeHistorySection(PhortuneAccount $account) { + $viewer = $this->getViewer(); + + $charges = id(new PhortuneChargeQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->needCarts(true) + ->setLimit(100) + ->execute(); + + $phids = array(); + foreach ($charges as $charge) { + $phids[] = $charge->getProviderPHID(); + $phids[] = $charge->getCartPHID(); + $phids[] = $charge->getMerchantPHID(); + $phids[] = $charge->getPaymentMethodPHID(); + } + + $handles = $this->loadViewerHandles($phids); + + $charges_uri = $this->getApplicationURI($account->getID().'/charge/'); + + $table = id(new PhortuneChargeTableView()) + ->setUser($viewer) + ->setCharges($charges) + ->setHandles($handles); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Recent Charges')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-list') + ->setHref($charges_uri) + ->setText(pht('View All Charges'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountDetailsController.php b/src/applications/phortune/controller/account/PhortuneAccountDetailsController.php new file mode 100644 index 0000000000..476c802597 --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountDetailsController.php @@ -0,0 +1,132 @@ +loadAccount(); + if ($response) { + return $response; + } + + $account = $this->getAccount(); + $title = $account->getName(); + + $viewer = $this->getViewer(); + + $invoices = id(new PhortuneCartQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->needPurchases(true) + ->withInvoices(true) + ->execute(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setBorder(true); + + $header = $this->buildHeaderView(); + + $details = $this->newDetailsView($account); + + $curtain = $this->buildCurtainView($account); + + $timeline = $this->buildTransactionTimeline( + $account, + new PhortuneAccountTransactionQuery()); + $timeline->setShouldTerminate(true); + + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn( + array( + $details, + $timeline, + )); + + $navigation = $this->buildSideNavView('details'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + + } + + private function buildCurtainView(PhortuneAccount $account) { + $viewer = $this->getViewer(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $account, + PhabricatorPolicyCapability::CAN_EDIT); + + $edit_uri = $this->getApplicationURI('account/edit/'.$account->getID().'/'); + + $curtain = $this->newCurtainView($account); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Account')) + ->setIcon('fa-pencil') + ->setHref($edit_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + $member_phids = $account->getMemberPHIDs(); + $handles = $viewer->loadHandles($member_phids); + + $member_list = id(new PHUIObjectItemListView()) + ->setSimple(true); + + foreach ($member_phids as $member_phid) { + $image_uri = $handles[$member_phid]->getImageURI(); + $image_href = $handles[$member_phid]->getURI(); + $person = $handles[$member_phid]; + + $member = id(new PHUIObjectItemView()) + ->setImageURI($image_uri) + ->setHref($image_href) + ->setHeader($person->getFullName()); + + $member_list->addItem($member); + } + + $curtain->newPanel() + ->setHeaderText(pht('Managers')) + ->appendChild($member_list); + + return $curtain; + } + + private function newDetailsView(PhortuneAccount $account) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $view->addProperty(pht('Account Name'), $account->getName()); + + $display_name = $account->getBillingName(); + if (!strlen($display_name)) { + $display_name = phutil_tag('em', array(), pht('None')); + } + + $display_address = $account->getBillingAddress(); + if (!strlen($display_address)) { + $display_address = phutil_tag('em', array(), pht('None')); + } else { + $display_address = phutil_escape_html_newlines($display_address); + } + + $view->addProperty(pht('Billing Name'), $display_name); + $view->addProperty(pht('Billing Address'), $display_address); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Account Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addPropertyList($view); + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountManagerController.php b/src/applications/phortune/controller/account/PhortuneAccountManagersController.php similarity index 93% rename from src/applications/phortune/controller/account/PhortuneAccountManagerController.php rename to src/applications/phortune/controller/account/PhortuneAccountManagersController.php index 502fbfe52e..f79d15a7f3 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountManagerController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountManagersController.php @@ -1,6 +1,6 @@ setTag('a') - ->setText(pht('New Manager')) + ->setText(pht('Add Managers')) ->setIcon('fa-plus') ->setWorkflow(true) ->setDisabled(!$can_edit) - ->setHref("/phortune/account/manager/add/{$id}/"); + ->setHref("/phortune/account/{$id}/managers/add/"); $header = id(new PHUIHeaderView()) ->setHeader(pht('Account Managers')) diff --git a/src/applications/phortune/controller/account/PhortuneAccountOrdersController.php b/src/applications/phortune/controller/account/PhortuneAccountOrdersController.php new file mode 100644 index 0000000000..38327a7099 --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountOrdersController.php @@ -0,0 +1,37 @@ +loadAccount(); + if ($response) { + return $response; + } + + $account = $this->getAccount(); + $title = $account->getName(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Order History')); + + $header = $this->buildHeaderView(); + $order_history = $this->newRecentOrdersView($account, 100); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter( + array( + $order_history, + )); + + $navigation = $this->buildSideNavView('orders'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountViewController.php b/src/applications/phortune/controller/account/PhortuneAccountOverviewController.php similarity index 51% rename from src/applications/phortune/controller/account/PhortuneAccountViewController.php rename to src/applications/phortune/controller/account/PhortuneAccountOverviewController.php index 6537577921..52b8127ab3 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountViewController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountOverviewController.php @@ -1,6 +1,6 @@ buildHeaderView(); - $curtain = $this->buildCurtainView($account); $status = $this->buildStatusView($account, $invoices); $invoices = $this->buildInvoicesSection($account, $invoices); - $purchase_history = $this->buildPurchaseHistorySection($account); - - $timeline = $this->buildTransactionTimeline( - $account, - new PhortuneAccountTransactionQuery()); - $timeline->setShouldTerminate(true); + $purchase_history = $this->newRecentOrdersView($account, 10); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setCurtain($curtain) - ->setMainColumn(array( - $status, - $invoices, - $purchase_history, - $timeline, - )); + ->setFooter( + array( + $status, + $invoices, + $purchase_history, + )); $navigation = $this->buildSideNavView('overview'); @@ -53,7 +46,6 @@ final class PhortuneAccountViewController ->setCrumbs($crumbs) ->setNavigation($navigation) ->appendChild($view); - } private function buildStatusView(PhortuneAccount $account, $invoices) { @@ -67,51 +59,6 @@ final class PhortuneAccountViewController return $view; } - private function buildCurtainView(PhortuneAccount $account) { - $viewer = $this->getViewer(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $account, - PhabricatorPolicyCapability::CAN_EDIT); - - $edit_uri = $this->getApplicationURI('account/edit/'.$account->getID().'/'); - - $curtain = $this->newCurtainView($account); - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Account')) - ->setIcon('fa-pencil') - ->setHref($edit_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - - $member_phids = $account->getMemberPHIDs(); - $handles = $viewer->loadHandles($member_phids); - - $member_list = id(new PHUIObjectItemListView()) - ->setSimple(true); - - foreach ($member_phids as $member_phid) { - $image_uri = $handles[$member_phid]->getImageURI(); - $image_href = $handles[$member_phid]->getURI(); - $person = $handles[$member_phid]; - - $member = id(new PHUIObjectItemView()) - ->setImageURI($image_uri) - ->setHref($image_href) - ->setHeader($person->getFullName()); - - $member_list->addItem($member); - } - - $curtain->newPanel() - ->setHeaderText(pht('Managers')) - ->appendChild($member_list); - - return $curtain; - } - private function buildInvoicesSection( PhortuneAccount $account, array $carts) { @@ -144,55 +91,6 @@ final class PhortuneAccountViewController ->setTable($table); } - private function buildPurchaseHistorySection(PhortuneAccount $account) { - $viewer = $this->getViewer(); - - $carts = id(new PhortuneCartQuery()) - ->setViewer($viewer) - ->withAccountPHIDs(array($account->getPHID())) - ->needPurchases(true) - ->withStatuses( - array( - PhortuneCart::STATUS_PURCHASING, - PhortuneCart::STATUS_CHARGED, - PhortuneCart::STATUS_HOLD, - PhortuneCart::STATUS_REVIEW, - PhortuneCart::STATUS_PURCHASED, - )) - ->setLimit(10) - ->execute(); - - $phids = array(); - foreach ($carts as $cart) { - $phids[] = $cart->getPHID(); - foreach ($cart->getPurchases() as $purchase) { - $phids[] = $purchase->getPHID(); - } - } - $handles = $this->loadViewerHandles($phids); - - $orders_uri = $this->getApplicationURI($account->getID().'/order/'); - - $table = id(new PhortuneOrderTableView()) - ->setUser($viewer) - ->setCarts($carts) - ->setHandles($handles); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Recent Orders')) - ->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-list') - ->setHref($orders_uri) - ->setText(pht('View All Orders'))); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); - } - protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); diff --git a/src/applications/phortune/controller/account/PhortuneAccountBillingController.php b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php similarity index 65% rename from src/applications/phortune/controller/account/PhortuneAccountBillingController.php rename to src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php index 0660358af7..519199fc92 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountBillingController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php @@ -1,6 +1,6 @@ getName(); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Billing')); + $crumbs->addTextCrumb(pht('Payment Methods')); $header = $this->buildHeaderView(); $methods = $this->buildPaymentMethodsSection($account); - $charge_history = $this->buildChargeHistorySection($account); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFooter(array( - $methods, - $charge_history, - )); + ->setFooter( + array( + $methods, + )); - $navigation = $this->buildSideNavView('billing'); + $navigation = $this->buildSideNavView('methods'); return $this->newPage() ->setTitle($title) @@ -60,7 +59,7 @@ final class PhortuneAccountBillingController ->setUser($viewer) ->setFlush(true) ->setNoDataString( - pht('No payment methods associated with this account.')); + pht('There are no payment methods associated with this account.')); $methods = id(new PhortunePaymentMethodQuery()) ->setViewer($viewer) @@ -116,46 +115,4 @@ final class PhortuneAccountBillingController ->setObjectList($list); } - private function buildChargeHistorySection(PhortuneAccount $account) { - $viewer = $this->getViewer(); - - $charges = id(new PhortuneChargeQuery()) - ->setViewer($viewer) - ->withAccountPHIDs(array($account->getPHID())) - ->needCarts(true) - ->setLimit(10) - ->execute(); - - $phids = array(); - foreach ($charges as $charge) { - $phids[] = $charge->getProviderPHID(); - $phids[] = $charge->getCartPHID(); - $phids[] = $charge->getMerchantPHID(); - $phids[] = $charge->getPaymentMethodPHID(); - } - - $handles = $this->loadViewerHandles($phids); - - $charges_uri = $this->getApplicationURI($account->getID().'/charge/'); - - $table = id(new PhortuneChargeTableView()) - ->setUser($viewer) - ->setCharges($charges) - ->setHandles($handles); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Charge History')) - ->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-list') - ->setHref($charges_uri) - ->setText(pht('View All Charges'))); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); - } - } diff --git a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php index 2a5448d5cc..06934bb00d 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php @@ -43,21 +43,43 @@ abstract class PhortuneAccountProfileController 'fa-user-circle'); $nav->addFilter( - 'subscriptions', - pht('Subscriptions'), - $this->getApplicationURI("/account/subscription/{$id}/"), - 'fa-retweet'); + 'details', + pht('Account Details'), + $this->getApplicationURI("/account/{$id}/details/"), + 'fa-address-card-o'); + + $nav->addLabel(pht('Payments')); $nav->addFilter( - 'billing', - pht('Billing / History'), - $this->getApplicationURI("/account/billing/{$id}/"), + 'methods', + pht('Payment Methods'), + $this->getApplicationURI("/account/{$id}/methods/"), 'fa-credit-card'); + $nav->addFilter( + 'subscriptions', + pht('Subscriptions'), + $this->getApplicationURI("/account/{$id}/subscriptions/"), + 'fa-retweet'); + + $nav->addFilter( + 'orders', + pht('Order History'), + $this->getApplicationURI("/account/{$id}/orders/"), + 'fa-shopping-bag'); + + $nav->addFilter( + 'charges', + pht('Charge History'), + $this->getApplicationURI("/account/{$id}/charges/"), + 'fa-calculator'); + + $nav->addLabel(pht('Personnel')); + $nav->addFilter( 'managers', - pht('Managers'), - $this->getApplicationURI("/account/manager/{$id}/"), + pht('Account Managers'), + $this->getApplicationURI("/account/{$id}/managers/"), 'fa-group'); $nav->selectFilter($filter); @@ -65,4 +87,57 @@ abstract class PhortuneAccountProfileController return $nav; } + final protected function newRecentOrdersView( + PhortuneAccount $account, + $limit) { + + $viewer = $this->getViewer(); + + $carts = id(new PhortuneCartQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->needPurchases(true) + ->withStatuses( + array( + PhortuneCart::STATUS_PURCHASING, + PhortuneCart::STATUS_CHARGED, + PhortuneCart::STATUS_HOLD, + PhortuneCart::STATUS_REVIEW, + PhortuneCart::STATUS_PURCHASED, + )) + ->setLimit($limit) + ->execute(); + + $phids = array(); + foreach ($carts as $cart) { + $phids[] = $cart->getPHID(); + foreach ($cart->getPurchases() as $purchase) { + $phids[] = $purchase->getPHID(); + } + } + $handles = $this->loadViewerHandles($phids); + + $orders_uri = $this->getApplicationURI($account->getID().'/order/'); + + $table = id(new PhortuneOrderTableView()) + ->setUser($viewer) + ->setCarts($carts) + ->setHandles($handles); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Recent Orders')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-list') + ->setHref($orders_uri) + ->setText(pht('View All Orders'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } + + } diff --git a/src/applications/phortune/editor/PhortuneAccountEditEngine.php b/src/applications/phortune/editor/PhortuneAccountEditEngine.php index 1b6f9a5040..d7272965c2 100644 --- a/src/applications/phortune/editor/PhortuneAccountEditEngine.php +++ b/src/applications/phortune/editor/PhortuneAccountEditEngine.php @@ -62,7 +62,11 @@ final class PhortuneAccountEditEngine } protected function getObjectViewURI($object) { - return $object->getURI(); + if ($this->getIsCreate()) { + return $object->getURI(); + } else { + return $object->getDetailsURI(); + } } protected function buildCustomEditFields($object) { diff --git a/src/applications/phortune/storage/PhortuneAccount.php b/src/applications/phortune/storage/PhortuneAccount.php index ade98d327f..2fb2a40589 100644 --- a/src/applications/phortune/storage/PhortuneAccount.php +++ b/src/applications/phortune/storage/PhortuneAccount.php @@ -103,6 +103,12 @@ final class PhortuneAccount extends PhortuneDAO return '/phortune/'.$this->getID().'/'; } + public function getDetailsURI() { + return urisprintf( + '/phortune/account/%d/details/', + $this->getID()); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ From e3ba53078e1be289601f53799c219543744f90a0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Aug 2019 12:14:58 -0700 Subject: [PATCH 12/27] Add scaffolding for ad-hoc email addresses associated with Phortune accounts Summary: Depends on D20697. Ref T8389. Add support for adding "billing@enterprise.com" and similar to Phortune accounts. Test Plan: Added and edited email addresses for a payment account. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T8389 Differential Revision: https://secure.phabricator.com/D20713 --- .../autopatches/20190802.email.01.storage.sql | 12 ++ .../autopatches/20190802.email.02.xaction.sql | 19 +++ src/__phutil_library_map__.php | 31 +++++ .../PhabricatorPhortuneApplication.php | 10 ++ .../constants/PhortuneAccountEmailStatus.php | 31 +++++ .../account/PhortuneAccountController.php | 6 +- ...hortuneAccountEmailAddressesController.php | 90 +++++++++++++ .../PhortuneAccountEmailEditController.php | 28 ++++ .../PhortuneAccountEmailViewController.php | 93 ++++++++++++++ .../PhortuneAccountProfileController.php | 6 + .../editor/PhortuneAccountEmailEditEngine.php | 114 +++++++++++++++++ .../editor/PhortuneAccountEmailEditor.php | 36 ++++++ .../phid/PhortuneAccountEmailPHIDType.php | 41 ++++++ .../query/PhortuneAccountEmailQuery.php | 91 +++++++++++++ .../PhortuneAccountEmailTransactionQuery.php | 10 ++ .../phortune/storage/PhortuneAccount.php | 6 + .../phortune/storage/PhortuneAccountEmail.php | 121 ++++++++++++++++++ .../PhortuneAccountEmailTransaction.php | 18 +++ ...PhortuneAccountEmailAddressTransaction.php | 63 +++++++++ .../PhortuneAccountEmailTransactionType.php | 4 + 20 files changed, 829 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20190802.email.01.storage.sql create mode 100644 resources/sql/autopatches/20190802.email.02.xaction.sql create mode 100644 src/applications/phortune/constants/PhortuneAccountEmailStatus.php create mode 100644 src/applications/phortune/controller/account/PhortuneAccountEmailAddressesController.php create mode 100644 src/applications/phortune/controller/account/PhortuneAccountEmailEditController.php create mode 100644 src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php create mode 100644 src/applications/phortune/editor/PhortuneAccountEmailEditEngine.php create mode 100644 src/applications/phortune/editor/PhortuneAccountEmailEditor.php create mode 100644 src/applications/phortune/phid/PhortuneAccountEmailPHIDType.php create mode 100644 src/applications/phortune/query/PhortuneAccountEmailQuery.php create mode 100644 src/applications/phortune/query/PhortuneAccountEmailTransactionQuery.php create mode 100644 src/applications/phortune/storage/PhortuneAccountEmail.php create mode 100644 src/applications/phortune/storage/PhortuneAccountEmailTransaction.php create mode 100644 src/applications/phortune/xaction/PhortuneAccountEmailAddressTransaction.php create mode 100644 src/applications/phortune/xaction/PhortuneAccountEmailTransactionType.php diff --git a/resources/sql/autopatches/20190802.email.01.storage.sql b/resources/sql/autopatches/20190802.email.01.storage.sql new file mode 100644 index 0000000000..f362067e6d --- /dev/null +++ b/resources/sql/autopatches/20190802.email.01.storage.sql @@ -0,0 +1,12 @@ +CREATE TABLE {$NAMESPACE}_phortune.phortune_accountemail ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + accountPHID VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + address VARCHAR(128) NOT NULL COLLATE {$COLLATE_SORT}, + status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + addressKey VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + accessKey VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20190802.email.02.xaction.sql b/resources/sql/autopatches/20190802.email.02.xaction.sql new file mode 100644 index 0000000000..d65f8d8b32 --- /dev/null +++ b/resources/sql/autopatches/20190802.email.02.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_phortune.phortune_accountemailtransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) NOT NULL, + oldValue LONGTEXT NOT NULL, + newValue LONGTEXT NOT NULL, + contentSource LONGTEXT NOT NULL, + metadata LONGTEXT NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 100f7fc30a..89861a8e42 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5229,6 +5229,19 @@ phutil_register_library_map(array( 'PhortuneAccountEditController' => 'applications/phortune/controller/account/PhortuneAccountEditController.php', 'PhortuneAccountEditEngine' => 'applications/phortune/editor/PhortuneAccountEditEngine.php', 'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php', + 'PhortuneAccountEmail' => 'applications/phortune/storage/PhortuneAccountEmail.php', + 'PhortuneAccountEmailAddressTransaction' => 'applications/phortune/xaction/PhortuneAccountEmailAddressTransaction.php', + 'PhortuneAccountEmailAddressesController' => 'applications/phortune/controller/account/PhortuneAccountEmailAddressesController.php', + 'PhortuneAccountEmailEditController' => 'applications/phortune/controller/account/PhortuneAccountEmailEditController.php', + 'PhortuneAccountEmailEditEngine' => 'applications/phortune/editor/PhortuneAccountEmailEditEngine.php', + 'PhortuneAccountEmailEditor' => 'applications/phortune/editor/PhortuneAccountEmailEditor.php', + 'PhortuneAccountEmailPHIDType' => 'applications/phortune/phid/PhortuneAccountEmailPHIDType.php', + 'PhortuneAccountEmailQuery' => 'applications/phortune/query/PhortuneAccountEmailQuery.php', + 'PhortuneAccountEmailStatus' => 'applications/phortune/constants/PhortuneAccountEmailStatus.php', + 'PhortuneAccountEmailTransaction' => 'applications/phortune/storage/PhortuneAccountEmailTransaction.php', + 'PhortuneAccountEmailTransactionQuery' => 'applications/phortune/query/PhortuneAccountEmailTransactionQuery.php', + 'PhortuneAccountEmailTransactionType' => 'applications/phortune/xaction/PhortuneAccountEmailTransactionType.php', + 'PhortuneAccountEmailViewController' => 'applications/phortune/controller/account/PhortuneAccountEmailViewController.php', 'PhortuneAccountHasMemberEdgeType' => 'applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php', 'PhortuneAccountListController' => 'applications/phortune/controller/account/PhortuneAccountListController.php', 'PhortuneAccountManagersController' => 'applications/phortune/controller/account/PhortuneAccountManagersController.php', @@ -11764,6 +11777,24 @@ phutil_register_library_map(array( 'PhortuneAccountEditController' => 'PhortuneController', 'PhortuneAccountEditEngine' => 'PhabricatorEditEngine', 'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhortuneAccountEmail' => array( + 'PhortuneDAO', + 'PhabricatorApplicationTransactionInterface', + 'PhabricatorPolicyInterface', + 'PhabricatorExtendedPolicyInterface', + ), + 'PhortuneAccountEmailAddressTransaction' => 'PhortuneAccountEmailTransactionType', + 'PhortuneAccountEmailAddressesController' => 'PhortuneAccountProfileController', + 'PhortuneAccountEmailEditController' => 'PhortuneAccountController', + 'PhortuneAccountEmailEditEngine' => 'PhabricatorEditEngine', + 'PhortuneAccountEmailEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhortuneAccountEmailPHIDType' => 'PhabricatorPHIDType', + 'PhortuneAccountEmailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhortuneAccountEmailStatus' => 'Phobject', + 'PhortuneAccountEmailTransaction' => 'PhabricatorModularTransaction', + 'PhortuneAccountEmailTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhortuneAccountEmailTransactionType' => 'PhabricatorModularTransactionType', + 'PhortuneAccountEmailViewController' => 'PhortuneAccountController', 'PhortuneAccountHasMemberEdgeType' => 'PhabricatorEdgeType', 'PhortuneAccountListController' => 'PhortuneController', 'PhortuneAccountManagersController' => 'PhortuneAccountProfileController', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index de3c740e2b..fad3d19c66 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -80,8 +80,18 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { '' => 'PhortuneAccountManagersController', 'add/' => 'PhortuneAccountAddManagerController', ), + 'addresses/' => array( + '' => 'PhortuneAccountEmailAddressesController', + $this->getEditRoutePattern('edit/') + => 'PhortuneAccountEmailEditController', + ), ), ), + 'address/' => array( + '(?P\d+)/' => 'PhortuneAccountEmailViewController', + $this->getEditRoutePattern('edit/') + => 'PhortuneAccountEmailEditController', + ), 'product/' => array( '' => 'PhortuneProductListController', 'view/(?P\d+)/' => 'PhortuneProductViewController', diff --git a/src/applications/phortune/constants/PhortuneAccountEmailStatus.php b/src/applications/phortune/constants/PhortuneAccountEmailStatus.php new file mode 100644 index 0000000000..fa18c3f466 --- /dev/null +++ b/src/applications/phortune/constants/PhortuneAccountEmailStatus.php @@ -0,0 +1,31 @@ + array( + 'name' => pht('Active'), + 'closed' => false, + ), + self::STATUS_DISABLED => array( + 'name' => pht('Disabled'), + 'closed' => true, + ), + self::STATUS_UNSUBSCRIBED => array( + 'name' => pht('Unsubscribed'), + 'closed' => true, + ), + ); + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountController.php b/src/applications/phortune/controller/account/PhortuneAccountController.php index 2ba3b393a2..a7361254e2 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountController.php @@ -9,6 +9,11 @@ abstract class PhortuneAccountController return $this->account; } + protected function setAccount(PhortuneAccount $account) { + $this->account = $account; + return $this; + } + protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); @@ -29,7 +34,6 @@ abstract class PhortuneAccountController return $this->loadAccountForEdit(); } - protected function loadAccountForEdit() { $viewer = $this->getViewer(); $request = $this->getRequest(); diff --git a/src/applications/phortune/controller/account/PhortuneAccountEmailAddressesController.php b/src/applications/phortune/controller/account/PhortuneAccountEmailAddressesController.php new file mode 100644 index 0000000000..1abff653f1 --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountEmailAddressesController.php @@ -0,0 +1,90 @@ +loadAccount(); + if ($response) { + return $response; + } + + $account = $this->getAccount(); + $title = $account->getName(); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Email Addresses')); + + $header = $this->buildHeaderView(); + $addresses = $this->buildAddressesSection($account); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter( + array( + $addresses, + )); + + $navigation = $this->buildSideNavView('addresses'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + } + + private function buildAddressesSection(PhortuneAccount $account) { + $viewer = $this->getViewer(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $account, + PhabricatorPolicyCapability::CAN_EDIT); + + $id = $account->getID(); + + $add = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Add Address')) + ->setIcon('fa-plus') + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit) + ->setHref("/phortune/account/{$id}/addresses/edit/"); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Billing Email Addresses')) + ->addActionLink($add); + + $addresses = id(new PhortuneAccountEmailQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->execute(); + + $list = id(new PHUIObjectItemListView()) + ->setUser($viewer) + ->setNoDataString( + pht( + 'There are no billing email addresses associated '. + 'with this account.')); + + $addresses = id(new PhortuneAccountEmailQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->execute(); + foreach ($addresses as $address) { + $item = id(new PHUIObjectItemView()) + ->setObjectName($address->getObjectName()) + ->setHeader($address->getAddress()) + ->setHref($address->getURI()); + + $list->addItem($item); + } + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountEmailEditController.php b/src/applications/phortune/controller/account/PhortuneAccountEmailEditController.php new file mode 100644 index 0000000000..0257d574dd --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountEmailEditController.php @@ -0,0 +1,28 @@ +setController($this); + + if (!$request->getURIData('id')) { + + if (!$request->getURIData('accountID')) { + return new Aphront404Response(); + } + + $response = $this->loadAccount(); + if ($response) { + return $response; + } + + $account = $this->getAccount(); + + $engine->setAccount($account); + } + + return $engine->buildResponse(); + } +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php b/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php new file mode 100644 index 0000000000..c9cb2570c3 --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php @@ -0,0 +1,93 @@ +getViewer(); + + $address = id(new PhortuneAccountEmailQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->executeOne(); + if (!$address) { + return new Aphront404Response(); + } + + $account = $address->getAccount(); + $this->setAccount($account); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Email Addresses'), $account->getEmailAddressesURI()) + ->addTextCrumb($address->getObjectName()) + ->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Account Email: %s', $address->getAddress())); + + $details = $this->newDetailsView($address); + + $timeline = $this->buildTransactionTimeline( + $address, + new PhortuneAccountEmailTransactionQuery()); + $timeline->setShouldTerminate(true); + + $curtain = $this->buildCurtainView($address); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn( + array( + $details, + $timeline, + )); + + return $this->newPage() + ->setTitle($address->getObjectName()) + ->setCrumbs($crumbs) + ->appendChild($view); + + } + + private function buildCurtainView(PhortuneAccountEmail $address) { + $viewer = $this->getViewer(); + $account = $address->getAccount(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $address, + PhabricatorPolicyCapability::CAN_EDIT); + + $edit_uri = $this->getApplicationURI( + urisprintf( + 'address/edit/%d/', + $address->getID())); + + $curtain = $this->newCurtainView($account); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Address')) + ->setIcon('fa-pencil') + ->setHref($edit_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + return $curtain; + } + + private function newDetailsView(PhortuneAccountEmail $address) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $view->addProperty(pht('Email Address'), $address->getAddress()); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Email Address Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addPropertyList($view); + } +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php index 06934bb00d..9de7b0a8de 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php @@ -82,6 +82,12 @@ abstract class PhortuneAccountProfileController $this->getApplicationURI("/account/{$id}/managers/"), 'fa-group'); + $nav->addFilter( + 'addresses', + pht('Email Addresses'), + $this->getApplicationURI("/account/{$id}/addresses/"), + 'fa-envelope-o'); + $nav->selectFilter($filter); return $nav; diff --git a/src/applications/phortune/editor/PhortuneAccountEmailEditEngine.php b/src/applications/phortune/editor/PhortuneAccountEmailEditEngine.php new file mode 100644 index 0000000000..c732e4215f --- /dev/null +++ b/src/applications/phortune/editor/PhortuneAccountEmailEditEngine.php @@ -0,0 +1,114 @@ +account = $account; + return $this; + } + + public function getAccount() { + return $this->account; + } + + public function getEngineName() { + return pht('Phortune Account Emails'); + } + + public function getEngineApplicationClass() { + return 'PhabricatorPhortuneApplication'; + } + + public function getSummaryHeader() { + return pht('Configure Phortune Account Email Forms'); + } + + public function getSummaryText() { + return pht( + 'Configure creation and editing forms for Phortune Account '. + 'Email Addresses.'); + } + + public function isEngineConfigurable() { + return false; + } + + protected function newEditableObject() { + $viewer = $this->getViewer(); + + $account = $this->getAccount(); + if (!$account) { + $account = new PhortuneAccount(); + } + + return PhortuneAccountEmail::initializeNewAddress( + $account, + $viewer->getPHID()); + } + + protected function newObjectQuery() { + return new PhortuneAccountEmailQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Add Email Address'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Account Email: %s', $object->getAddress()); + } + + protected function getObjectEditShortText($object) { + return pht('%s', $object->getAddress()); + } + + protected function getObjectCreateShortText() { + return pht('Add Email Address'); + } + + protected function getObjectName() { + return pht('Account Email'); + } + + protected function getObjectCreateCancelURI($object) { + return $this->getAccount()->getEmailAddressesURI(); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('address/edit/'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + + if ($this->getIsCreate()) { + $address_field = id(new PhabricatorTextEditField()) + ->setTransactionType( + PhortuneAccountEmailAddressTransaction::TRANSACTIONTYPE) + ->setIsRequired(true); + } else { + $address_field = new PhabricatorStaticEditField(); + } + + $address_field + ->setKey('address') + ->setLabel(pht('Email Address')) + ->setDescription(pht('Email address.')) + ->setConduitTypeDescription(pht('New email address.')) + ->setValue($object->getAddress()); + + return array( + $address_field, + ); + } + +} diff --git a/src/applications/phortune/editor/PhortuneAccountEmailEditor.php b/src/applications/phortune/editor/PhortuneAccountEmailEditor.php new file mode 100644 index 0000000000..40d12a97ba --- /dev/null +++ b/src/applications/phortune/editor/PhortuneAccountEmailEditor.php @@ -0,0 +1,36 @@ +getAddress()), + null); + + throw new PhabricatorApplicationTransactionValidationException($errors); + } + +} diff --git a/src/applications/phortune/phid/PhortuneAccountEmailPHIDType.php b/src/applications/phortune/phid/PhortuneAccountEmailPHIDType.php new file mode 100644 index 0000000000..fccd50cf16 --- /dev/null +++ b/src/applications/phortune/phid/PhortuneAccountEmailPHIDType.php @@ -0,0 +1,41 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $email = $objects[$phid]; + + $id = $email->getID(); + + $handle->setName($email->getObjectName()); + } + } + +} diff --git a/src/applications/phortune/query/PhortuneAccountEmailQuery.php b/src/applications/phortune/query/PhortuneAccountEmailQuery.php new file mode 100644 index 0000000000..4494372ef6 --- /dev/null +++ b/src/applications/phortune/query/PhortuneAccountEmailQuery.php @@ -0,0 +1,91 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withAccountPHIDs(array $phids) { + $this->accountPHIDs = $phids; + return $this; + } + + public function newResultObject() { + return new PhortuneAccountEmail(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function willFilterPage(array $addresses) { + $accounts = id(new PhortuneAccountQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withPHIDs(mpull($addresses, 'getAccountPHID')) + ->execute(); + $accounts = mpull($accounts, null, 'getPHID'); + + foreach ($addresses as $key => $address) { + $account = idx($accounts, $address->getAccountPHID()); + + if (!$account) { + $this->didRejectResult($addresses[$key]); + unset($addresses[$key]); + continue; + } + + $address->attachAccount($account); + } + + return $addresses; + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'address.id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'address.phid IN (%Ls)', + $this->phids); + } + + if ($this->accountPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'address.accountPHID IN (%Ls)', + $this->accountPHIDs); + } + + return $where; + } + + public function getQueryApplicationClass() { + return 'PhabricatorPhortuneApplication'; + } + + protected function getPrimaryTableAlias() { + return 'address'; + } + +} diff --git a/src/applications/phortune/query/PhortuneAccountEmailTransactionQuery.php b/src/applications/phortune/query/PhortuneAccountEmailTransactionQuery.php new file mode 100644 index 0000000000..2aa9d8418e --- /dev/null +++ b/src/applications/phortune/query/PhortuneAccountEmailTransactionQuery.php @@ -0,0 +1,10 @@ +getID()); } + public function getEmailAddressesURI() { + return urisprintf( + '/phortune/account/%d/addresses/', + $this->getID()); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/phortune/storage/PhortuneAccountEmail.php b/src/applications/phortune/storage/PhortuneAccountEmail.php new file mode 100644 index 0000000000..674a86be4b --- /dev/null +++ b/src/applications/phortune/storage/PhortuneAccountEmail.php @@ -0,0 +1,121 @@ + true, + self::CONFIG_COLUMN_SCHEMA => array( + 'address' => 'sort128', + 'status' => 'text32', + 'addressKey' => 'text32', + 'accessKey' => 'text32', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_account' => array( + 'columns' => array('accountPHID', 'address'), + 'unique' => true, + ), + 'key_address' => array( + 'columns' => array('addressKey'), + ), + ), + ) + parent::getConfiguration(); + } + + public function getPHIDType() { + return PhortuneAccountEmailPHIDType::TYPECONST; + } + + public static function initializeNewAddress( + PhortuneAccount $account, + $author_phid) { + + $address_key = Filesystem::readRandomCharacters(16); + $access_key = Filesystem::readRandomCharacters(16); + $default_status = PhortuneAccountEmailStatus::getDefaultStatusConstant(); + + return id(new self()) + ->setAuthorPHID($author_phid) + ->setAccountPHID($account->getPHID()) + ->setStatus($default_status) + ->attachAccount($account) + ->setAddressKey($address_key) + ->setAccessKey($access_key); + } + + public function attachAccount(PhortuneAccount $account) { + $this->account = $account; + return $this; + } + + public function getAccount() { + return $this->assertAttached($this->account); + } + + public function getObjectName() { + return pht('Account Email %d', $this->getID()); + } + + public function getURI() { + return urisprintf( + '/phortune/address/%d/', + $this->getID()); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + return PhabricatorPolicies::getMostOpenPolicy(); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + +/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ + + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + return array( + array($this->getAccount(), $capability), + ); + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhortuneAccountEmailEditor(); + } + + public function getApplicationTransactionTemplate() { + return new PhortuneAccountEmailTransaction(); + } + +} diff --git a/src/applications/phortune/storage/PhortuneAccountEmailTransaction.php b/src/applications/phortune/storage/PhortuneAccountEmailTransaction.php new file mode 100644 index 0000000000..699c209438 --- /dev/null +++ b/src/applications/phortune/storage/PhortuneAccountEmailTransaction.php @@ -0,0 +1,18 @@ +getAddress(); + } + + public function applyInternalEffects($object, $value) { + $object->setAddress($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getAddress(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('You must provide an email address.')); + } + + $max_length = $object->getColumnMaximumByteLength('address'); + foreach ($xactions as $xaction) { + $old_value = $xaction->getOldValue(); + $new_value = $xaction->getNewValue(); + + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht( + 'The address can be no longer than %s characters.', + new PhutilNumber($max_length)), + $xaction); + continue; + } + + if (!PhabricatorUserEmail::isValidAddress($new_value)) { + $errors[] = $this->newInvalidError( + PhabricatorUserEmail::describeValidAddresses(), + $xaction); + continue; + } + + if ($new_value !== $old_value) { + if (!$this->isNewObject()) { + $errors[] = $this->newInvalidError( + pht( + 'Account email addresses can not be edited once they are '. + 'created. To change the billing address for an account, '. + 'disable the old address and then add a new address.'), + $xaction); + continue; + } + } + + } + + return $errors; + } + +} diff --git a/src/applications/phortune/xaction/PhortuneAccountEmailTransactionType.php b/src/applications/phortune/xaction/PhortuneAccountEmailTransactionType.php new file mode 100644 index 0000000000..2654796207 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneAccountEmailTransactionType.php @@ -0,0 +1,4 @@ + Date: Thu, 15 Aug 2019 16:06:56 -0700 Subject: [PATCH 13/27] In Phortune, write relationships between payment accounts and merchants they interact with Summary: Depends on D20713. Ref T13366. When a payment account establishes a relationship with a merchant by creating a cart or subscription, create an edge to give the merchant access to view the payment account. Also, migrate all existing subscriptions and carts to write these edges. This aims at straightening out Phortune permissions, which are currently a bit wonky on a couple of dimensions. See T13366 for detailed discussion. Test Plan: - Created and edited carts/subscriptions, saw edges write. - Ran migrations, saw edges write. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13366 Differential Revision: https://secure.phabricator.com/D20715 --- .../autopatches/20190815.account.01.carts.php | 10 ++++++++ .../20190815.account.02.subscriptions.php | 10 ++++++++ src/__phutil_library_map__.php | 4 +++ .../PhortuneAccountDetailsController.php | 22 ++++++++++++++++ .../PhortuneAccountHasMerchantEdgeType.php | 11 ++++++++ .../PhortuneMerchantHasAccountEdgeType.php | 12 +++++++++ .../phortune/editor/PhortuneCartEditor.php | 12 +++++++++ .../phid/PhortuneMerchantPHIDType.php | 11 ++++---- .../phortune/query/PhortuneAccountQuery.php | 25 +++++++++++++++++-- .../phortune/storage/PhortuneAccount.php | 22 ++++++++++++++++ .../phortune/storage/PhortuneSubscription.php | 4 +++ 11 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 resources/sql/autopatches/20190815.account.01.carts.php create mode 100644 resources/sql/autopatches/20190815.account.02.subscriptions.php create mode 100644 src/applications/phortune/edge/PhortuneAccountHasMerchantEdgeType.php create mode 100644 src/applications/phortune/edge/PhortuneMerchantHasAccountEdgeType.php diff --git a/resources/sql/autopatches/20190815.account.01.carts.php b/resources/sql/autopatches/20190815.account.01.carts.php new file mode 100644 index 0000000000..2332dd642f --- /dev/null +++ b/resources/sql/autopatches/20190815.account.01.carts.php @@ -0,0 +1,10 @@ +addEdge($cart->getAccountPHID(), $edge_type, $cart->getMerchantPHID()) + ->save(); +} diff --git a/resources/sql/autopatches/20190815.account.02.subscriptions.php b/resources/sql/autopatches/20190815.account.02.subscriptions.php new file mode 100644 index 0000000000..38db05b0ef --- /dev/null +++ b/resources/sql/autopatches/20190815.account.02.subscriptions.php @@ -0,0 +1,10 @@ +addEdge($sub->getAccountPHID(), $edge_type, $sub->getMerchantPHID()) + ->save(); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 89861a8e42..4a93b0c95b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5243,6 +5243,7 @@ phutil_register_library_map(array( 'PhortuneAccountEmailTransactionType' => 'applications/phortune/xaction/PhortuneAccountEmailTransactionType.php', 'PhortuneAccountEmailViewController' => 'applications/phortune/controller/account/PhortuneAccountEmailViewController.php', 'PhortuneAccountHasMemberEdgeType' => 'applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php', + 'PhortuneAccountHasMerchantEdgeType' => 'applications/phortune/edge/PhortuneAccountHasMerchantEdgeType.php', 'PhortuneAccountListController' => 'applications/phortune/controller/account/PhortuneAccountListController.php', 'PhortuneAccountManagersController' => 'applications/phortune/controller/account/PhortuneAccountManagersController.php', 'PhortuneAccountNameTransaction' => 'applications/phortune/xaction/PhortuneAccountNameTransaction.php', @@ -5302,6 +5303,7 @@ phutil_register_library_map(array( 'PhortuneMerchantEditController' => 'applications/phortune/controller/merchant/PhortuneMerchantEditController.php', 'PhortuneMerchantEditEngine' => 'applications/phortune/editor/PhortuneMerchantEditEngine.php', 'PhortuneMerchantEditor' => 'applications/phortune/editor/PhortuneMerchantEditor.php', + 'PhortuneMerchantHasAccountEdgeType' => 'applications/phortune/edge/PhortuneMerchantHasAccountEdgeType.php', 'PhortuneMerchantHasMemberEdgeType' => 'applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php', 'PhortuneMerchantInvoiceCreateController' => 'applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php', 'PhortuneMerchantInvoiceEmailTransaction' => 'applications/phortune/xaction/PhortuneMerchantInvoiceEmailTransaction.php', @@ -11796,6 +11798,7 @@ phutil_register_library_map(array( 'PhortuneAccountEmailTransactionType' => 'PhabricatorModularTransactionType', 'PhortuneAccountEmailViewController' => 'PhortuneAccountController', 'PhortuneAccountHasMemberEdgeType' => 'PhabricatorEdgeType', + 'PhortuneAccountHasMerchantEdgeType' => 'PhabricatorEdgeType', 'PhortuneAccountListController' => 'PhortuneController', 'PhortuneAccountManagersController' => 'PhortuneAccountProfileController', 'PhortuneAccountNameTransaction' => 'PhortuneAccountTransactionType', @@ -11866,6 +11869,7 @@ phutil_register_library_map(array( 'PhortuneMerchantEditController' => 'PhortuneMerchantController', 'PhortuneMerchantEditEngine' => 'PhabricatorEditEngine', 'PhortuneMerchantEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhortuneMerchantHasAccountEdgeType' => 'PhabricatorEdgeType', 'PhortuneMerchantHasMemberEdgeType' => 'PhabricatorEdgeType', 'PhortuneMerchantInvoiceCreateController' => 'PhortuneMerchantProfileController', 'PhortuneMerchantInvoiceEmailTransaction' => 'PhortuneMerchantTransactionType', diff --git a/src/applications/phortune/controller/account/PhortuneAccountDetailsController.php b/src/applications/phortune/controller/account/PhortuneAccountDetailsController.php index 476c802597..67fbda78ec 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountDetailsController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountDetailsController.php @@ -97,6 +97,28 @@ final class PhortuneAccountDetailsController ->setHeaderText(pht('Managers')) ->appendChild($member_list); + $merchant_list = id(new PHUIObjectItemListView()) + ->setSimple(true) + ->setNoDataString(pht('No purchase history.')); + + $merchant_phids = $account->getMerchantPHIDs(); + $handles = $viewer->loadHandles($merchant_phids); + + foreach ($merchant_phids as $merchant_phid) { + $handle = $handles[$merchant_phid]; + + $merchant = id(new PHUIObjectItemView()) + ->setImageURI($handle->getImageURI()) + ->setHref($handle->getURI()) + ->setHeader($handle->getFullName()); + + $merchant_list->addItem($merchant); + } + + $curtain->newPanel() + ->setHeaderText(pht('Merchants')) + ->appendChild($merchant_list); + return $curtain; } diff --git a/src/applications/phortune/edge/PhortuneAccountHasMerchantEdgeType.php b/src/applications/phortune/edge/PhortuneAccountHasMerchantEdgeType.php new file mode 100644 index 0000000000..3da3da95dd --- /dev/null +++ b/src/applications/phortune/edge/PhortuneAccountHasMerchantEdgeType.php @@ -0,0 +1,11 @@ +getAccount(); + $merchant = $object->getMerchant(); + $account->writeMerchantEdge($merchant); + + return $xactions; + } + + } diff --git a/src/applications/phortune/phid/PhortuneMerchantPHIDType.php b/src/applications/phortune/phid/PhortuneMerchantPHIDType.php index 941f704ab8..69b93582b3 100644 --- a/src/applications/phortune/phid/PhortuneMerchantPHIDType.php +++ b/src/applications/phortune/phid/PhortuneMerchantPHIDType.php @@ -21,7 +21,8 @@ final class PhortuneMerchantPHIDType extends PhabricatorPHIDType { array $phids) { return id(new PhortuneMerchantQuery()) - ->withPHIDs($phids); + ->withPHIDs($phids) + ->needProfileImage(true); } public function loadHandles( @@ -32,10 +33,10 @@ final class PhortuneMerchantPHIDType extends PhabricatorPHIDType { foreach ($handles as $phid => $handle) { $merchant = $objects[$phid]; - $id = $merchant->getID(); - - $handle->setName($merchant->getName()); - $handle->setURI("/phortune/merchant/{$id}/"); + $handle + ->setName($merchant->getName()) + ->setURI($merchant->getURI()) + ->setImageURI($merchant->getProfileImageURI()); } } diff --git a/src/applications/phortune/query/PhortuneAccountQuery.php b/src/applications/phortune/query/PhortuneAccountQuery.php index ee8291218c..70c12d9722 100644 --- a/src/applications/phortune/query/PhortuneAccountQuery.php +++ b/src/applications/phortune/query/PhortuneAccountQuery.php @@ -53,13 +53,34 @@ final class PhortuneAccountQuery protected function willFilterPage(array $accounts) { $query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(mpull($accounts, 'getPHID')) - ->withEdgeTypes(array(PhortuneAccountHasMemberEdgeType::EDGECONST)); + ->withEdgeTypes( + array( + PhortuneAccountHasMemberEdgeType::EDGECONST, + PhortuneAccountHasMerchantEdgeType::EDGECONST, + )); + $query->execute(); foreach ($accounts as $account) { - $member_phids = $query->getDestinationPHIDs(array($account->getPHID())); + $member_phids = $query->getDestinationPHIDs( + array( + $account->getPHID(), + ), + array( + PhortuneAccountHasMemberEdgeType::EDGECONST, + )); $member_phids = array_reverse($member_phids); $account->attachMemberPHIDs($member_phids); + + $merchant_phids = $query->getDestinationPHIDs( + array( + $account->getPHID(), + ), + array( + PhortuneAccountHasMerchantEdgeType::EDGECONST, + )); + $merchant_phids = array_reverse($merchant_phids); + $account->attachMerchantPHIDs($merchant_phids); } return $accounts; diff --git a/src/applications/phortune/storage/PhortuneAccount.php b/src/applications/phortune/storage/PhortuneAccount.php index 16ff6f72d0..6b31805671 100644 --- a/src/applications/phortune/storage/PhortuneAccount.php +++ b/src/applications/phortune/storage/PhortuneAccount.php @@ -16,11 +16,13 @@ final class PhortuneAccount extends PhortuneDAO protected $billingAddress; private $memberPHIDs = self::ATTACHABLE; + private $merchantPHIDs = self::ATTACHABLE; public static function initializeNewAccount(PhabricatorUser $actor) { return id(new self()) ->setBillingName('') ->setBillingAddress('') + ->attachMerchantPHIDs(array()) ->attachMemberPHIDs(array()); } @@ -115,6 +117,26 @@ final class PhortuneAccount extends PhortuneDAO $this->getID()); } + public function attachMerchantPHIDs(array $merchant_phids) { + $this->merchantPHIDs = $merchant_phids; + return $this; + } + + public function getMerchantPHIDs() { + return $this->assertAttached($this->merchantPHIDs); + } + + public function writeMerchantEdge(PhortuneMerchant $merchant) { + $edge_src = $this->getPHID(); + $edge_type = PhortuneAccountHasMerchantEdgeType::EDGECONST; + $edge_dst = $merchant->getPHID(); + + id(new PhabricatorEdgeEditor()) + ->addEdge($edge_src, $edge_type, $edge_dst) + ->save(); + + return $this; + } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/phortune/storage/PhortuneSubscription.php b/src/applications/phortune/storage/PhortuneSubscription.php index a996dbf5d2..41fc80c2e7 100644 --- a/src/applications/phortune/storage/PhortuneSubscription.php +++ b/src/applications/phortune/storage/PhortuneSubscription.php @@ -161,6 +161,10 @@ final class PhortuneSubscription extends PhortuneDAO } $this->saveTransaction(); + $account = $this->getAccount(); + $merchant = $this->getMerchant(); + $account->writeMerchantEdge($merchant); + return $result; } From a3213ab20be6127e3b3db73533b0fd3c26826ae1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 15 Aug 2019 16:37:46 -0700 Subject: [PATCH 14/27] In Phortune, use actual merchant authority (not authority grants) to control account visibility Summary: Depends on D20715. Ref T13366. See that task for discussion. Replace the unreliable "grantAuthority()"-based check with an actual "can the viewer edit any merchant this account has a relationship with?" check. This makes these objects easier to use from a policy perspective and makes it so that the `Query` alone can fully enforce permissions properly with no setup, so general infrastructure (like handles and transactions) works properly with Phortune objects. Test Plan: Viewed merchants and accounts as users with no authority, direct authority on the account, and indirect authority via a merchant relationship. Maniphest Tasks: T13366 Differential Revision: https://secure.phabricator.com/D20716 --- ...hortuneMerchantInvoiceCreateController.php | 7 +- .../phortune/query/PhortuneMerchantQuery.php | 69 +++++++++++++++++-- .../phortune/storage/PhortuneAccount.php | 22 ++++-- 3 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php index 300525cdd2..8bd070853c 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php @@ -58,9 +58,10 @@ final class PhortuneMerchantInvoiceCreateController } if (!$target_account) { - $accounts = PhortuneAccountQuery::loadAccountsForUser( - $target_user, - PhabricatorContentSource::newFromRequest($request)); + $accounts = id(new PhortuneAccountQuery()) + ->setViewer($viewer) + ->withMemberPHIDs(array($target_user->getPHID())) + ->execute(); $form = id(new AphrontFormView()) ->setUser($viewer) diff --git a/src/applications/phortune/query/PhortuneMerchantQuery.php b/src/applications/phortune/query/PhortuneMerchantQuery.php index b6cab7dbc2..aef7d8aaf1 100644 --- a/src/applications/phortune/query/PhortuneMerchantQuery.php +++ b/src/applications/phortune/query/PhortuneMerchantQuery.php @@ -86,14 +86,14 @@ final class PhortuneMerchantQuery if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'merchant.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'merchant.phid IN (%Ls)', $this->phids); } @@ -113,7 +113,7 @@ final class PhortuneMerchantQuery if ($this->memberPHIDs !== null) { $joins[] = qsprintf( $conn, - 'LEFT JOIN %T e ON m.phid = e.src AND e.type = %d', + 'LEFT JOIN %T e ON merchant.phid = e.src AND e.type = %d', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhortuneMerchantHasMemberEdgeType::EDGECONST); } @@ -126,7 +126,68 @@ final class PhortuneMerchantQuery } protected function getPrimaryTableAlias() { - return 'm'; + return 'merchant'; + } + + public static function canViewersEditMerchants( + array $viewer_phids, + array $merchant_phids) { + + // See T13366 for some discussion. This is an unusual caching construct to + // make policy filtering of Accounts easier. + + foreach ($viewer_phids as $key => $viewer_phid) { + if (!$viewer_phid) { + unset($viewer_phids[$key]); + } + } + + if (!$viewer_phids) { + return array(); + } + + $cache_key = 'phortune.merchant.can-edit'; + $cache = PhabricatorCaches::getRequestCache(); + + $cache_data = $cache->getKey($cache_key); + if (!$cache_data) { + $cache_data = array(); + } + + $load_phids = array(); + foreach ($viewer_phids as $viewer_phid) { + if (!isset($cache_data[$viewer_phid])) { + $load_phids[] = $viewer_phid; + } + } + + $did_write = false; + foreach ($load_phids as $load_phid) { + $merchants = id(new self()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withMemberPHIDs(array($load_phid)) + ->execute(); + foreach ($merchants as $merchant) { + $cache_data[$load_phid][$merchant->getPHID()] = true; + $did_write = true; + } + } + + if ($did_write) { + $cache->setKey($cache_key, $cache_data); + } + + $results = array(); + foreach ($viewer_phids as $viewer_phid) { + foreach ($merchant_phids as $merchant_phid) { + if (!isset($cache_data[$viewer_phid][$merchant_phid])) { + continue; + } + $results[$viewer_phid][$merchant_phid] = true; + } + } + + return $results; } } diff --git a/src/applications/phortune/storage/PhortuneAccount.php b/src/applications/phortune/storage/PhortuneAccount.php index 6b31805671..182c80f40f 100644 --- a/src/applications/phortune/storage/PhortuneAccount.php +++ b/src/applications/phortune/storage/PhortuneAccount.php @@ -179,13 +179,18 @@ final class PhortuneAccount extends PhortuneDAO return true; } - // If the viewer is acting on behalf of a merchant, they can see - // payment accounts. + // See T13366. If the viewer can edit any merchant that this payment + // account has a relationship with, they can see the payment account. if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { - foreach ($viewer->getAuthorities() as $authority) { - if ($authority instanceof PhortuneMerchant) { - return true; - } + $viewer_phids = array($viewer->getPHID()); + $merchant_phids = $this->getMerchantPHIDs(); + + $any_edit = PhortuneMerchantQuery::canViewersEditMerchants( + $viewer_phids, + $merchant_phids); + + if ($any_edit) { + return true; } } @@ -193,7 +198,10 @@ final class PhortuneAccount extends PhortuneDAO } public function describeAutomaticCapability($capability) { - return pht('Members of an account can always view and edit it.'); + return array( + pht('Members of an account can always view and edit it.'), + pht('Merchants an account has established a relationship can view it.'), + ); } From 0cc7e8eeb850e2b01f9370585d221f529fc16428 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Aug 2019 09:23:59 -0700 Subject: [PATCH 15/27] Update Phortune payment account interfaces to handle merchant vs customer views Summary: Depends on D20716. Ref T13366. This implements the new policy behavior cleanly in all top-level Phortune payment account interfaces. Test Plan: As a merchant with an account relationship (not an account member) and an account member, browsed all account interfaces and attempted to perform edits. As a merchant, saw a reduced-strength view. Maniphest Tasks: T13366 Differential Revision: https://secure.phabricator.com/D20717 --- src/__phutil_library_map__.php | 6 +- .../PhabricatorPhortuneApplication.php | 12 +- .../PhortuneAccountAddManagerController.php | 41 +++--- .../PhortuneAccountChargesController.php | 16 ++- .../account/PhortuneAccountController.php | 136 ++++++++++++++---- .../PhortuneAccountDetailsController.php | 11 +- ...hortuneAccountEmailAddressesController.php | 16 ++- .../PhortuneAccountEmailEditController.php | 20 +-- .../PhortuneAccountEmailViewController.php | 14 +- .../PhortuneAccountManagersController.php | 23 +-- .../PhortuneAccountOrdersController.php | 17 ++- .../PhortuneAccountOverviewController.php | 11 +- ...hortuneAccountPaymentMethodsController.php | 16 ++- .../PhortuneAccountProfileController.php | 39 ++--- .../PhortuneAccountSubscriptionController.php | 24 ++-- .../PhortuneChargeListController.php} | 2 +- .../phortune/storage/PhortuneAccount.php | 14 +- .../phortune/storage/PhortuneAccountEmail.php | 3 +- src/view/layout/AphrontSideNavFilterView.php | 5 + 19 files changed, 277 insertions(+), 149 deletions(-) rename src/applications/phortune/controller/{account/PhortuneAccountChargeListController.php => charge/PhortuneChargeListController.php} (97%) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4a93b0c95b..dc935eb460 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5222,7 +5222,6 @@ phutil_register_library_map(array( 'PhortuneAccountAddManagerController' => 'applications/phortune/controller/account/PhortuneAccountAddManagerController.php', 'PhortuneAccountBillingAddressTransaction' => 'applications/phortune/xaction/PhortuneAccountBillingAddressTransaction.php', 'PhortuneAccountBillingNameTransaction' => 'applications/phortune/xaction/PhortuneAccountBillingNameTransaction.php', - 'PhortuneAccountChargeListController' => 'applications/phortune/controller/account/PhortuneAccountChargeListController.php', 'PhortuneAccountChargesController' => 'applications/phortune/controller/account/PhortuneAccountChargesController.php', 'PhortuneAccountController' => 'applications/phortune/controller/account/PhortuneAccountController.php', 'PhortuneAccountDetailsController' => 'applications/phortune/controller/account/PhortuneAccountDetailsController.php', @@ -5277,6 +5276,7 @@ phutil_register_library_map(array( 'PhortuneCartUpdateController' => 'applications/phortune/controller/cart/PhortuneCartUpdateController.php', 'PhortuneCartViewController' => 'applications/phortune/controller/cart/PhortuneCartViewController.php', 'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php', + 'PhortuneChargeListController' => 'applications/phortune/controller/charge/PhortuneChargeListController.php', 'PhortuneChargePHIDType' => 'applications/phortune/phid/PhortuneChargePHIDType.php', 'PhortuneChargeQuery' => 'applications/phortune/query/PhortuneChargeQuery.php', 'PhortuneChargeSearchEngine' => 'applications/phortune/query/PhortuneChargeSearchEngine.php', @@ -11769,10 +11769,9 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), - 'PhortuneAccountAddManagerController' => 'PhortuneController', + 'PhortuneAccountAddManagerController' => 'PhortuneAccountController', 'PhortuneAccountBillingAddressTransaction' => 'PhortuneAccountTransactionType', 'PhortuneAccountBillingNameTransaction' => 'PhortuneAccountTransactionType', - 'PhortuneAccountChargeListController' => 'PhortuneController', 'PhortuneAccountChargesController' => 'PhortuneAccountProfileController', 'PhortuneAccountController' => 'PhortuneController', 'PhortuneAccountDetailsController' => 'PhortuneAccountProfileController', @@ -11839,6 +11838,7 @@ phutil_register_library_map(array( 'PhortuneDAO', 'PhabricatorPolicyInterface', ), + 'PhortuneChargeListController' => 'PhortuneController', 'PhortuneChargePHIDType' => 'PhabricatorPHIDType', 'PhortuneChargeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneChargeSearchEngine' => 'PhabricatorApplicationSearchEngine', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index fad3d19c66..fbabe97f35 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -39,8 +39,6 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { 'card/' => array( 'new/' => 'PhortunePaymentMethodCreateController', ), - 'order/(?:query/(?P[^/]+)/)?' - => 'PhortuneCartListController', 'subscription/' => array( '(?:query/(?P[^/]+)/)?' => 'PhortuneSubscriptionListController', @@ -51,8 +49,10 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { 'order/(?P\d+)/' => 'PhortuneCartListController', ), + 'order/(?:query/(?P[^/]+)/)?' + => 'PhortuneCartListController', 'charge/(?:query/(?P[^/]+)/)?' - => 'PhortuneAccountChargeListController', + => 'PhortuneChargeListController', ), 'card/(?P\d+)/' => array( 'edit/' => 'PhortunePaymentMethodEditController', @@ -82,16 +82,12 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { ), 'addresses/' => array( '' => 'PhortuneAccountEmailAddressesController', + '(?P\d+)/' => 'PhortuneAccountEmailViewController', $this->getEditRoutePattern('edit/') => 'PhortuneAccountEmailEditController', ), ), ), - 'address/' => array( - '(?P\d+)/' => 'PhortuneAccountEmailViewController', - $this->getEditRoutePattern('edit/') - => 'PhortuneAccountEmailEditController', - ), 'product/' => array( '' => 'PhortuneProductListController', 'view/(?P\d+)/' => 'PhortuneProductViewController', diff --git a/src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php b/src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php index 0c8c71d968..4a2b42ab69 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountAddManagerController.php @@ -1,23 +1,17 @@ getViewer(); - $id = $request->getURIData('accountID'); + $account = $this->getAccount(); - $account = id(new PhortuneAccountQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$account) { - return new Aphront404Response(); - } + $id = $account->getID(); $v_managers = array(); $e_managers = null; @@ -53,12 +47,24 @@ final class PhortuneAccountAddManagerController extends PhortuneController { } } + $account_phid = $account->getPHID(); + $handles = $viewer->loadHandles(array($account_phid)); + $handle = $handles[$account_phid]; + $form = id(new AphrontFormView()) - ->setUser($viewer) + ->setViewer($viewer) + ->appendInstructions( + pht( + 'Choose one or more users to add as account managers. Managers '. + 'have full control of the account.')) + ->appendControl( + id(new AphrontFormStaticControl()) + ->setLabel(pht('Payment Account')) + ->setValue($handle->renderLink())) ->appendControl( id(new AphrontFormTokenizerControl()) ->setDatasource(new PhabricatorPeopleDatasource()) - ->setLabel(pht('Managers')) + ->setLabel(pht('Add Managers')) ->setName('managerPHIDs') ->setValue($v_managers) ->setError($e_managers)); @@ -69,7 +75,6 @@ final class PhortuneAccountAddManagerController extends PhortuneController { ->setWidth(AphrontDialogView::WIDTH_FORM) ->addCancelButton($account_uri) ->addSubmitButton(pht('Add Managers')); - } } diff --git a/src/applications/phortune/controller/account/PhortuneAccountChargesController.php b/src/applications/phortune/controller/account/PhortuneAccountChargesController.php index 6fc0209f94..a899059669 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountChargesController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountChargesController.php @@ -3,25 +3,27 @@ final class PhortuneAccountChargesController extends PhortuneAccountProfileController { - public function handleRequest(AphrontRequest $request) { - $response = $this->loadAccount(); - if ($response) { - return $response; - } + protected function shouldRequireAccountEditCapability() { + return false; + } + protected function handleAccountRequest(AphrontRequest $request) { $account = $this->getAccount(); $title = $account->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Order History')); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Order History')) + ->setBorder(true); $header = $this->buildHeaderView(); + $authority = $this->newAccountAuthorityView(); $charge_history = $this->buildChargeHistorySection($account); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setFooter( array( + $authority, $charge_history, )); diff --git a/src/applications/phortune/controller/account/PhortuneAccountController.php b/src/applications/phortune/controller/account/PhortuneAccountController.php index a7361254e2..ed48c67383 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountController.php @@ -4,14 +4,34 @@ abstract class PhortuneAccountController extends PhortuneController { private $account; + private $merchants; - protected function getAccount() { - return $this->account; + final public function handleRequest(AphrontRequest $request) { + if ($this->shouldRequireAccountEditCapability()) { + $response = $this->loadAccountForEdit(); + } else { + $response = $this->loadAccountForView(); + } + + if ($response) { + return $response; + } + + return $this->handleAccountRequest($request); } - protected function setAccount(PhortuneAccount $account) { - $this->account = $account; - return $this; + abstract protected function shouldRequireAccountEditCapability(); + abstract protected function handleAccountRequest(AphrontRequest $request); + + final protected function getAccount() { + if ($this->account === null) { + throw new Exception( + pht( + 'Unable to "getAccount()" before loading or setting account '. + 'context.')); + } + + return $this->account; } protected function buildApplicationCrumbs() { @@ -25,44 +45,112 @@ abstract class PhortuneAccountController return $crumbs; } - protected function loadAccount() { - // TODO: Currently, you must be able to edit an account to view the detail - // page, because the account must be broadly visible so merchants can - // process orders but merchants should not be able to see all the details - // of an account. Ideally the profile pages should be visible to merchants, - // too, just with less information. - return $this->loadAccountForEdit(); + private function loadAccountForEdit() { + return $this->loadAccountWithCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )); } - protected function loadAccountForEdit() { + private function loadAccountForView() { + return $this->loadAccountWithCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + )); + } + + private function loadAccountWithCapabilities(array $capabilities) { $viewer = $this->getViewer(); $request = $this->getRequest(); $account_id = $request->getURIData('accountID'); if (!$account_id) { - $account_id = $request->getURIData('id'); - } - - if (!$account_id) { - return new Aphront404Response(); + throw new Exception( + pht( + 'Controller ("%s") extends controller "%s", but is reachable '. + 'with no "accountID" in URI.', + get_class($this), + __CLASS__)); } $account = id(new PhortuneAccountQuery()) ->setViewer($viewer) ->withIDs(array($account_id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) + ->requireCapabilities($capabilities) ->executeOne(); if (!$account) { return new Aphront404Response(); } - $this->account = $account; + $this->setAccount($account); return null; } + private function setAccount(PhortuneAccount $account) { + $this->account = $account; + + $viewer = $this->getViewer(); + if (!$account->isUserAccountMember($viewer)) { + $merchant_phids = $account->getMerchantPHIDs(); + $merchants = id(new PhortuneMerchantQuery()) + ->setViewer($viewer) + ->withPHIDs($merchant_phids) + ->withMemberPHIDs(array($viewer->getPHID())) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + + $this->merchants = $merchants; + } else { + $this->merchants = array(); + } + + return $this; + } + + final protected function getMerchants() { + if ($this->merchants === null) { + throw new Exception( + pht( + 'Unable to "getMerchants()" before loading or setting account '. + 'context.')); + } + + return $this->merchants; + } + + final protected function newAccountAuthorityView() { + $viewer = $this->getViewer(); + + $merchants = $this->getMerchants(); + if (!$merchants) { + return null; + } + + $merchant_phids = mpull($merchants, 'getPHID'); + $merchant_handles = $viewer->loadHandles($merchant_phids); + $merchant_handles = iterator_to_array($merchant_handles); + + $merchant_list = mpull($merchant_handles, 'renderLink'); + $merchant_list = phutil_implode_html(', ', $merchant_list); + + $merchant_message = pht( + 'You can view this account because you control %d merchant(s) it '. + 'has a relationship with: %s.', + phutil_count($merchants), + $merchant_list); + + return id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->setErrors( + array( + $merchant_message, + )); + } + } diff --git a/src/applications/phortune/controller/account/PhortuneAccountDetailsController.php b/src/applications/phortune/controller/account/PhortuneAccountDetailsController.php index 67fbda78ec..e6003c8159 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountDetailsController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountDetailsController.php @@ -3,12 +3,11 @@ final class PhortuneAccountDetailsController extends PhortuneAccountProfileController { - public function handleRequest(AphrontRequest $request) { - $response = $this->loadAccount(); - if ($response) { - return $response; - } + protected function shouldRequireAccountEditCapability() { + return true; + } + protected function handleAccountRequest(AphrontRequest $request) { $account = $this->getAccount(); $title = $account->getName(); @@ -26,6 +25,7 @@ final class PhortuneAccountDetailsController $header = $this->buildHeaderView(); + $authority = $this->newAccountAuthorityView(); $details = $this->newDetailsView($account); $curtain = $this->buildCurtainView($account); @@ -41,6 +41,7 @@ final class PhortuneAccountDetailsController ->setCurtain($curtain) ->setMainColumn( array( + $authority, $details, $timeline, )); diff --git a/src/applications/phortune/controller/account/PhortuneAccountEmailAddressesController.php b/src/applications/phortune/controller/account/PhortuneAccountEmailAddressesController.php index 1abff653f1..a946e02efb 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountEmailAddressesController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountEmailAddressesController.php @@ -3,25 +3,27 @@ final class PhortuneAccountEmailAddressesController extends PhortuneAccountProfileController { - public function handleRequest(AphrontRequest $request) { - $response = $this->loadAccount(); - if ($response) { - return $response; - } + protected function shouldRequireAccountEditCapability() { + return true; + } + protected function handleAccountRequest(AphrontRequest $request) { $account = $this->getAccount(); $title = $account->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Email Addresses')); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Email Addresses')) + ->setBorder(true); $header = $this->buildHeaderView(); + $authority = $this->newAccountAuthorityView(); $addresses = $this->buildAddressesSection($account); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setFooter( array( + $authority, $addresses, )); diff --git a/src/applications/phortune/controller/account/PhortuneAccountEmailEditController.php b/src/applications/phortune/controller/account/PhortuneAccountEmailEditController.php index 0257d574dd..117139fd55 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountEmailEditController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountEmailEditController.php @@ -3,23 +3,17 @@ final class PhortuneAccountEmailEditController extends PhortuneAccountController { - public function handleRequest(AphrontRequest $request) { + protected function shouldRequireAccountEditCapability() { + return true; + } + + protected function handleAccountRequest(AphrontRequest $request) { + $account = $this->getAccount(); + $engine = id(new PhortuneAccountEmailEditEngine()) ->setController($this); if (!$request->getURIData('id')) { - - if (!$request->getURIData('accountID')) { - return new Aphront404Response(); - } - - $response = $this->loadAccount(); - if ($response) { - return $response; - } - - $account = $this->getAccount(); - $engine->setAccount($account); } diff --git a/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php b/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php index c9cb2570c3..7732518d72 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php @@ -3,20 +3,23 @@ final class PhortuneAccountEmailViewController extends PhortuneAccountController { - public function handleRequest(AphrontRequest $request) { + protected function shouldRequireAccountEditCapability() { + return true; + } + + protected function handleAccountRequest(AphrontRequest $request) { $viewer = $this->getViewer(); + $account = $this->getAccount(); $address = id(new PhortuneAccountEmailQuery()) ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) ->withIDs(array($request->getURIData('id'))) ->executeOne(); if (!$address) { return new Aphront404Response(); } - $account = $address->getAccount(); - $this->setAccount($account); - $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb(pht('Email Addresses'), $account->getEmailAddressesURI()) ->addTextCrumb($address->getObjectName()) @@ -61,7 +64,8 @@ final class PhortuneAccountEmailViewController $edit_uri = $this->getApplicationURI( urisprintf( - 'address/edit/%d/', + 'account/%d/addresses/edit/%d/', + $account->getID(), $address->getID())); $curtain = $this->newCurtainView($account); diff --git a/src/applications/phortune/controller/account/PhortuneAccountManagersController.php b/src/applications/phortune/controller/account/PhortuneAccountManagersController.php index f79d15a7f3..538fadeac9 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountManagersController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountManagersController.php @@ -3,26 +3,29 @@ final class PhortuneAccountManagersController extends PhortuneAccountProfileController { - public function handleRequest(AphrontRequest $request) { - $response = $this->loadAccount(); - if ($response) { - return $response; - } + protected function shouldRequireAccountEditCapability() { + return false; + } + protected function handleAccountRequest(AphrontRequest $request) { $account = $this->getAccount(); $title = $account->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Managers')); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Managers')) + ->setBorder(true); $header = $this->buildHeaderView(); + $authority = $this->newAccountAuthorityView(); $members = $this->buildMembersSection($account); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFooter(array( - $members, - )); + ->setFooter( + array( + $authority, + $members, + )); $navigation = $this->buildSideNavView('managers'); diff --git a/src/applications/phortune/controller/account/PhortuneAccountOrdersController.php b/src/applications/phortune/controller/account/PhortuneAccountOrdersController.php index 38327a7099..902d2032ff 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountOrdersController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountOrdersController.php @@ -3,25 +3,28 @@ final class PhortuneAccountOrdersController extends PhortuneAccountProfileController { - public function handleRequest(AphrontRequest $request) { - $response = $this->loadAccount(); - if ($response) { - return $response; - } + protected function shouldRequireAccountEditCapability() { + return false; + } + protected function handleAccountRequest(AphrontRequest $request) { $account = $this->getAccount(); $title = $account->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Order History')); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Order History')) + ->setBorder(true); $header = $this->buildHeaderView(); + $authority = $this->newAccountAuthorityView(); + $order_history = $this->newRecentOrdersView($account, 100); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setFooter( array( + $authority, $order_history, )); diff --git a/src/applications/phortune/controller/account/PhortuneAccountOverviewController.php b/src/applications/phortune/controller/account/PhortuneAccountOverviewController.php index 52b8127ab3..078599faca 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountOverviewController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountOverviewController.php @@ -3,12 +3,11 @@ final class PhortuneAccountOverviewController extends PhortuneAccountProfileController { - public function handleRequest(AphrontRequest $request) { - $response = $this->loadAccount(); - if ($response) { - return $response; - } + protected function shouldRequireAccountEditCapability() { + return false; + } + protected function handleAccountRequest(AphrontRequest $request) { $account = $this->getAccount(); $title = $account->getName(); @@ -26,6 +25,7 @@ final class PhortuneAccountOverviewController $header = $this->buildHeaderView(); + $authority = $this->newAccountAuthorityView(); $status = $this->buildStatusView($account, $invoices); $invoices = $this->buildInvoicesSection($account, $invoices); $purchase_history = $this->newRecentOrdersView($account, 10); @@ -34,6 +34,7 @@ final class PhortuneAccountOverviewController ->setHeader($header) ->setFooter( array( + $authority, $status, $invoices, $purchase_history, diff --git a/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php index 519199fc92..e80e1a0302 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php @@ -3,18 +3,19 @@ final class PhortuneAccountPaymentMethodsController extends PhortuneAccountProfileController { - public function handleRequest(AphrontRequest $request) { - $response = $this->loadAccount(); - if ($response) { - return $response; - } + protected function shouldRequireAccountEditCapability() { + return false; + } + protected function handleAccountRequest(AphrontRequest $request) { $account = $this->getAccount(); $title = $account->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Payment Methods')); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Payment Methods')) + ->setBorder(true); + $authority = $this->newAccountAuthorityView(); $header = $this->buildHeaderView(); $methods = $this->buildPaymentMethodsSection($account); @@ -22,6 +23,7 @@ final class PhortuneAccountPaymentMethodsController ->setHeader($header) ->setFooter( array( + $authority, $methods, )); diff --git a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php index 9de7b0a8de..522ee08108 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php @@ -17,13 +17,16 @@ abstract class PhortuneAccountProfileController ->setHeader($title) ->setHeaderIcon('fa-user-circle'); - return $header; - } + if ($this->getMerchants()) { + $customer_tag = id(new PHUITagView()) + ->setType(PHUITagView::TYPE_SHADE) + ->setName(pht('Customer Account')) + ->setColor('indigo') + ->setIcon('fa-credit-card'); + $header->addTag($customer_tag); + } - protected function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - $crumbs->setBorder(true); - return $crumbs; + return $header; } protected function buildSideNavView($filter = null) { @@ -31,6 +34,8 @@ abstract class PhortuneAccountProfileController $account = $this->getAccount(); $id = $account->getID(); + $can_edit = !$this->getMerchants(); + $nav = id(new AphrontSideNavFilterView()) ->setBaseURI(new PhutilURI($this->getApplicationURI())); @@ -42,11 +47,12 @@ abstract class PhortuneAccountProfileController $this->getApplicationURI("/{$id}/"), 'fa-user-circle'); - $nav->addFilter( - 'details', - pht('Account Details'), - $this->getApplicationURI("/account/{$id}/details/"), - 'fa-address-card-o'); + $nav->newLink('details') + ->setName(pht('Account Details')) + ->setHref($this->getApplicationURI("/account/{$id}/details/")) + ->setIcon('fa-address-card-o') + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit); $nav->addLabel(pht('Payments')); @@ -82,11 +88,12 @@ abstract class PhortuneAccountProfileController $this->getApplicationURI("/account/{$id}/managers/"), 'fa-group'); - $nav->addFilter( - 'addresses', - pht('Email Addresses'), - $this->getApplicationURI("/account/{$id}/addresses/"), - 'fa-envelope-o'); + $nav->newLink('addresses') + ->setname(pht('Email Addresses')) + ->setHref($this->getApplicationURI("/account/{$id}/addresses/")) + ->setIcon('fa-envelope-o') + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit); $nav->selectFilter($filter); diff --git a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php index 418507e9c2..3acefffd6c 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php @@ -3,26 +3,30 @@ final class PhortuneAccountSubscriptionController extends PhortuneAccountProfileController { - public function handleRequest(AphrontRequest $request) { - $response = $this->loadAccount(); - if ($response) { - return $response; - } + protected function shouldRequireAccountEditCapability() { + return false; + } + protected function handleAccountRequest(AphrontRequest $request) { $account = $this->getAccount(); $title = $account->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Subscriptions')); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Subscriptions')) + ->setBorder(true); $header = $this->buildHeaderView(); + $authority = $this->newAccountAuthorityView(); + $subscriptions = $this->buildSubscriptionsSection($account); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFooter(array( - $subscriptions, - )); + ->setFooter( + array( + $authority, + $subscriptions, + )); $navigation = $this->buildSideNavView('subscriptions'); diff --git a/src/applications/phortune/controller/account/PhortuneAccountChargeListController.php b/src/applications/phortune/controller/charge/PhortuneChargeListController.php similarity index 97% rename from src/applications/phortune/controller/account/PhortuneAccountChargeListController.php rename to src/applications/phortune/controller/charge/PhortuneChargeListController.php index ed3f901675..b8edb92507 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountChargeListController.php +++ b/src/applications/phortune/controller/charge/PhortuneChargeListController.php @@ -1,6 +1,6 @@ getPHID(); + if (!$user_phid) { + return null; + } + + $member_map = array_fuse($this->getMemberPHIDs()); + + return isset($member_map[$user_phid]); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -174,8 +185,7 @@ final class PhortuneAccount extends PhortuneDAO } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - $members = array_fuse($this->getMemberPHIDs()); - if (isset($members[$viewer->getPHID()])) { + if ($this->isUserAccountMember($viewer)) { return true; } diff --git a/src/applications/phortune/storage/PhortuneAccountEmail.php b/src/applications/phortune/storage/PhortuneAccountEmail.php index 674a86be4b..5f1ede8414 100644 --- a/src/applications/phortune/storage/PhortuneAccountEmail.php +++ b/src/applications/phortune/storage/PhortuneAccountEmail.php @@ -73,7 +73,8 @@ final class PhortuneAccountEmail public function getURI() { return urisprintf( - '/phortune/address/%d/', + '/phortune/account/%d/addresses/%d/', + $this->getAccount()->getID(), $this->getID()); } diff --git a/src/view/layout/AphrontSideNavFilterView.php b/src/view/layout/AphrontSideNavFilterView.php index 9abcea5a02..8757a935b0 100644 --- a/src/view/layout/AphrontSideNavFilterView.php +++ b/src/view/layout/AphrontSideNavFilterView.php @@ -111,6 +111,11 @@ final class AphrontSideNavFilterView extends AphrontView { $key, $name, $uri, PHUIListItemView::TYPE_BUTTON); } + public function newLink($key) { + $this->addFilter($key, ''); + return $this->getMenuView()->getItem($key); + } + private function addThing($key, $name, $uri, $type, $icon = null) { $item = id(new PHUIListItemView()) ->setName($name) From c4e0ac4d2783096e90e7bfddddc648844189c3db Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Aug 2019 10:04:57 -0700 Subject: [PATCH 16/27] Update PhortunePaymentMethod for modern policy interfaces Summary: Depends on D20717. Ref T13366. Make PhortunePaymentMethod use an extended policy interface for consistency with modern approaches. Since Accounts have hard-coded policy behavior (and can't have object policies like "Subscribers") this should have no actual impact on program behavior. This leaves one weird piece in the policy dialog UIs, see T13381. Test Plan: Viewed and edited payment methods as a merchant and account member. Merchants can only view, not edit. Maniphest Tasks: T13366 Differential Revision: https://secure.phabricator.com/D20718 --- src/__phutil_library_map__.php | 4 ++ .../PhortunePaymentMethodPolicyCodex.php | 35 ++++++++++++ ...hortuneAccountPaymentMethodsController.php | 1 - .../query/PhortunePaymentMethodQuery.php | 3 ++ .../storage/PhortunePaymentMethod.php | 54 +++++++++++++++---- .../policy/codex/PhabricatorPolicyCodex.php | 10 ---- 6 files changed, 87 insertions(+), 20 deletions(-) create mode 100644 src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index dc935eb460..5ab1daf723 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5329,6 +5329,7 @@ phutil_register_library_map(array( 'PhortunePaymentMethodDisableController' => 'applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php', 'PhortunePaymentMethodEditController' => 'applications/phortune/controller/payment/PhortunePaymentMethodEditController.php', 'PhortunePaymentMethodPHIDType' => 'applications/phortune/phid/PhortunePaymentMethodPHIDType.php', + 'PhortunePaymentMethodPolicyCodex' => 'applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php', 'PhortunePaymentMethodQuery' => 'applications/phortune/query/PhortunePaymentMethodQuery.php', 'PhortunePaymentProvider' => 'applications/phortune/provider/PhortunePaymentProvider.php', 'PhortunePaymentProviderConfig' => 'applications/phortune/storage/PhortunePaymentProviderConfig.php', @@ -11893,11 +11894,14 @@ phutil_register_library_map(array( 'PhortunePaymentMethod' => array( 'PhortuneDAO', 'PhabricatorPolicyInterface', + 'PhabricatorExtendedPolicyInterface', + 'PhabricatorPolicyCodexInterface', ), 'PhortunePaymentMethodCreateController' => 'PhortuneController', 'PhortunePaymentMethodDisableController' => 'PhortuneController', 'PhortunePaymentMethodEditController' => 'PhortuneController', 'PhortunePaymentMethodPHIDType' => 'PhabricatorPHIDType', + 'PhortunePaymentMethodPolicyCodex' => 'PhabricatorPolicyCodex', 'PhortunePaymentMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortunePaymentProvider' => 'Phobject', 'PhortunePaymentProviderConfig' => array( diff --git a/src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php b/src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php new file mode 100644 index 0000000000..99b41508e8 --- /dev/null +++ b/src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php @@ -0,0 +1,35 @@ +getObject(); + + $rules = array(); + + $rules[] = $this->newRule() + ->setCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + )) + ->setIsActive(true) + ->setDescription( + pht( + 'Account members may view and edit payment methods.')); + + $rules[] = $this->newRule() + ->setCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + )) + ->setIsActive(true) + ->setDescription( + pht( + 'Merchants you have a relationship with may view associated '. + 'payment methods.')); + + return $rules; + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php index e80e1a0302..ec10205451 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php @@ -34,7 +34,6 @@ final class PhortuneAccountPaymentMethodsController ->setCrumbs($crumbs) ->setNavigation($navigation) ->appendChild($view); - } private function buildPaymentMethodsSection(PhortuneAccount $account) { diff --git a/src/applications/phortune/query/PhortunePaymentMethodQuery.php b/src/applications/phortune/query/PhortunePaymentMethodQuery.php index 42d54805e6..013fa147ec 100644 --- a/src/applications/phortune/query/PhortunePaymentMethodQuery.php +++ b/src/applications/phortune/query/PhortunePaymentMethodQuery.php @@ -53,6 +53,7 @@ final class PhortunePaymentMethodQuery $account = idx($accounts, $method->getAccountPHID()); if (!$account) { unset($methods[$key]); + $this->didRejectResult($method); continue; } $method->attachAccount($account); @@ -72,6 +73,7 @@ final class PhortunePaymentMethodQuery $merchant = idx($merchants, $method->getMerchantPHID()); if (!$merchant) { unset($methods[$key]); + $this->didRejectResult($method); continue; } $method->attachMerchant($merchant); @@ -91,6 +93,7 @@ final class PhortunePaymentMethodQuery $provider_config = idx($provider_configs, $method->getProviderPHID()); if (!$provider_config) { unset($methods[$key]); + $this->didRejectResult($method); continue; } $method->attachProviderConfig($provider_config); diff --git a/src/applications/phortune/storage/PhortunePaymentMethod.php b/src/applications/phortune/storage/PhortunePaymentMethod.php index 8937d6ee84..9aa88c1cf3 100644 --- a/src/applications/phortune/storage/PhortunePaymentMethod.php +++ b/src/applications/phortune/storage/PhortunePaymentMethod.php @@ -4,8 +4,12 @@ * A payment method is a credit card; it is associated with an account and * charges can be made against it. */ -final class PhortunePaymentMethod extends PhortuneDAO - implements PhabricatorPolicyInterface { +final class PhortunePaymentMethod + extends PhortuneDAO + implements + PhabricatorPolicyInterface, + PhabricatorExtendedPolicyInterface, + PhabricatorPolicyCodexInterface { const STATUS_ACTIVE = 'payment:active'; const STATUS_DISABLED = 'payment:disabled'; @@ -148,18 +152,50 @@ final class PhortunePaymentMethod extends PhortuneDAO } public function getPolicy($capability) { - return $this->getAccount()->getPolicy($capability); + return PhabricatorPolicies::getMostOpenPolicy(); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - return $this->getAccount()->hasAutomaticCapability( - $capability, - $viewer); + + // See T13366. If you can edit the merchant associated with this payment + // method, you can view the payment method. + if ($capability === PhabricatorPolicyCapability::CAN_VIEW) { + $any_edit = PhortuneMerchantQuery::canViewersEditMerchants( + array($viewer->getPHID()), + array($this->getMerchantPHID())); + if ($any_edit) { + return true; + } + } + + return false; } - public function describeAutomaticCapability($capability) { - return pht( - 'Members of an account can always view and edit its payment methods.'); + +/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ + + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + if ($this->hasAutomaticCapability($capability, $viewer)) { + return array(); + } + + // See T13366. For blanket view and edit permissions on all payment + // methods, you must be able to edit the associated account. + return array( + array( + $this->getAccount(), + PhabricatorPolicyCapability::CAN_EDIT, + ), + ); + } + + +/* -( PhabricatorPolicyCodexInterface )------------------------------------ */ + + + public function newPolicyCodex() { + return new PhortunePaymentMethodPolicyCodex(); } } diff --git a/src/applications/policy/codex/PhabricatorPolicyCodex.php b/src/applications/policy/codex/PhabricatorPolicyCodex.php index 48e6d2f557..8dee2a38d1 100644 --- a/src/applications/policy/codex/PhabricatorPolicyCodex.php +++ b/src/applications/policy/codex/PhabricatorPolicyCodex.php @@ -44,16 +44,6 @@ abstract class PhabricatorPolicyCodex return null; } - final public function getPolicySpecialRuleForCapability($capability) { - foreach ($this->getPolicySpecialRuleDescriptions() as $rule) { - if (in_array($capability, $rule->getCapabilities())) { - return $rule; - } - } - - return null; - } - final protected function newRule() { return new PhabricatorPolicyCodexRuleDescription(); } From 201634848eb644a8ad565796ff56d71860667f49 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Aug 2019 10:36:30 -0700 Subject: [PATCH 17/27] Make Phortune payment methods transaction-oriented and always support "Add Payment Method" Summary: Depends on D20718. Ref T13366. Ref T13367. - Phortune payment methods currently do not use transactions; update them. - Give them a proper view page with a transaction log. - Add an "Add Payment Method" button which always works. - Show which subscriptions a payment method is associated with. - Get rid of the "Active" status indicator since we now treat "disabled" as "removed", to align with user expectation/intent. - Swap out of some of the super weird div-form-button UI into the new "big, clickable" UI for choice dialogs among a small number of options on a single dimension. Test Plan: - As a mechant-authority and account-authority, created payment methods from carts, subscriptions, and accounts. Edited and viewed payment methods. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13367, T13366 Differential Revision: https://secure.phabricator.com/D20719 --- resources/celerity/map.php | 4 +- .../20190816.payment.01.xaction.sql | 19 + src/__phutil_library_map__.php | 25 +- .../PhabricatorPhortuneApplication.php | 5 +- .../account/PhortuneAccountController.php | 10 +- ...uneAccountPaymentMethodListController.php} | 45 +- ...tuneAccountPaymentMethodViewController.php | 154 ++++++ .../PhortuneAccountSubscriptionController.php | 3 - .../PhortunePaymentMethodCreateController.php | 303 ------------ .../PhortunePaymentMethodCreateController.php | 462 ++++++++++++++++++ ...PhortunePaymentMethodDisableController.php | 19 +- .../PhortunePaymentMethodEditController.php | 46 +- .../editor/PhortunePaymentMethodEditor.php | 18 + .../PhortunePaymentMethodTransactionQuery.php | 10 + .../query/PhortuneSubscriptionQuery.php | 46 +- .../PhortuneSubscriptionSearchEngine.php | 13 - .../phortune/storage/PhortuneAccount.php | 6 + .../phortune/storage/PhortuneMerchant.php | 4 + .../storage/PhortunePaymentMethod.php | 26 +- .../PhortunePaymentMethodTransaction.php | 18 + .../view/PhortuneSubscriptionTableView.php | 20 +- .../PhortunePaymentMethodNameTransaction.php | 39 ++ ...PhortunePaymentMethodStatusTransaction.php | 22 + .../PhortunePaymentMethodTransactionType.php | 4 + .../css/application/phortune/phortune.css | 9 - 25 files changed, 906 insertions(+), 424 deletions(-) create mode 100644 resources/sql/autopatches/20190816.payment.01.xaction.sql rename src/applications/phortune/controller/account/{PhortuneAccountPaymentMethodsController.php => PhortuneAccountPaymentMethodListController.php} (63%) create mode 100644 src/applications/phortune/controller/account/PhortuneAccountPaymentMethodViewController.php delete mode 100644 src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php create mode 100644 src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodCreateController.php rename src/applications/phortune/controller/{payment => paymentmethod}/PhortunePaymentMethodDisableController.php (71%) rename src/applications/phortune/controller/{payment => paymentmethod}/PhortunePaymentMethodEditController.php (62%) create mode 100644 src/applications/phortune/editor/PhortunePaymentMethodEditor.php create mode 100644 src/applications/phortune/query/PhortunePaymentMethodTransactionQuery.php create mode 100644 src/applications/phortune/storage/PhortunePaymentMethodTransaction.php create mode 100644 src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodNameTransaction.php create mode 100644 src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodStatusTransaction.php create mode 100644 src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodTransactionType.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4eeb433e96..37794cfb81 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -92,7 +92,7 @@ return array( 'rsrc/css/application/pholio/pholio.css' => '88ef5ef1', 'rsrc/css/application/phortune/phortune-credit-card-form.css' => '3b9868a8', 'rsrc/css/application/phortune/phortune-invoice.css' => '4436b241', - 'rsrc/css/application/phortune/phortune.css' => '12e8251a', + 'rsrc/css/application/phortune/phortune.css' => '508a1a5e', 'rsrc/css/application/phrequent/phrequent.css' => 'bd79cc67', 'rsrc/css/application/phriction/phriction-document-css.css' => '03380da0', 'rsrc/css/application/policy/policy-edit.css' => '8794e2ed', @@ -810,7 +810,7 @@ return array( 'pholio-inline-comments-css' => '722b48c2', 'phortune-credit-card-form' => 'd12d214f', 'phortune-credit-card-form-css' => '3b9868a8', - 'phortune-css' => '12e8251a', + 'phortune-css' => '508a1a5e', 'phortune-invoice-css' => '4436b241', 'phrequent-css' => 'bd79cc67', 'phriction-document-css' => '03380da0', diff --git a/resources/sql/autopatches/20190816.payment.01.xaction.sql b/resources/sql/autopatches/20190816.payment.01.xaction.sql new file mode 100644 index 0000000000..22d7baae7e --- /dev/null +++ b/resources/sql/autopatches/20190816.payment.01.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_phortune.phortune_paymentmethodtransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) NOT NULL, + oldValue LONGTEXT NOT NULL, + newValue LONGTEXT NOT NULL, + contentSource LONGTEXT NOT NULL, + metadata LONGTEXT NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5ab1daf723..fbf708ca1d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5249,7 +5249,8 @@ phutil_register_library_map(array( 'PhortuneAccountOrdersController' => 'applications/phortune/controller/account/PhortuneAccountOrdersController.php', 'PhortuneAccountOverviewController' => 'applications/phortune/controller/account/PhortuneAccountOverviewController.php', 'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php', - 'PhortuneAccountPaymentMethodsController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php', + 'PhortuneAccountPaymentMethodListController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php', + 'PhortuneAccountPaymentMethodViewController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodViewController.php', 'PhortuneAccountProfileController' => 'applications/phortune/controller/account/PhortuneAccountProfileController.php', 'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php', 'PhortuneAccountSubscriptionController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionController.php', @@ -5325,12 +5326,18 @@ phutil_register_library_map(array( 'PhortuneOrderTableView' => 'applications/phortune/view/PhortuneOrderTableView.php', 'PhortunePayPalPaymentProvider' => 'applications/phortune/provider/PhortunePayPalPaymentProvider.php', 'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php', - 'PhortunePaymentMethodCreateController' => 'applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php', - 'PhortunePaymentMethodDisableController' => 'applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php', - 'PhortunePaymentMethodEditController' => 'applications/phortune/controller/payment/PhortunePaymentMethodEditController.php', + 'PhortunePaymentMethodCreateController' => 'applications/phortune/controller/paymentmethod/PhortunePaymentMethodCreateController.php', + 'PhortunePaymentMethodDisableController' => 'applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php', + 'PhortunePaymentMethodEditController' => 'applications/phortune/controller/paymentmethod/PhortunePaymentMethodEditController.php', + 'PhortunePaymentMethodEditor' => 'applications/phortune/editor/PhortunePaymentMethodEditor.php', + 'PhortunePaymentMethodNameTransaction' => 'applications/phortune/xaction/paymentmethod/PhortunePaymentMethodNameTransaction.php', 'PhortunePaymentMethodPHIDType' => 'applications/phortune/phid/PhortunePaymentMethodPHIDType.php', 'PhortunePaymentMethodPolicyCodex' => 'applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php', 'PhortunePaymentMethodQuery' => 'applications/phortune/query/PhortunePaymentMethodQuery.php', + 'PhortunePaymentMethodStatusTransaction' => 'applications/phortune/xaction/paymentmethod/PhortunePaymentMethodStatusTransaction.php', + 'PhortunePaymentMethodTransaction' => 'applications/phortune/storage/PhortunePaymentMethodTransaction.php', + 'PhortunePaymentMethodTransactionQuery' => 'applications/phortune/query/PhortunePaymentMethodTransactionQuery.php', + 'PhortunePaymentMethodTransactionType' => 'applications/phortune/xaction/paymentmethod/PhortunePaymentMethodTransactionType.php', 'PhortunePaymentProvider' => 'applications/phortune/provider/PhortunePaymentProvider.php', 'PhortunePaymentProviderConfig' => 'applications/phortune/storage/PhortunePaymentProviderConfig.php', 'PhortunePaymentProviderConfigEditor' => 'applications/phortune/editor/PhortunePaymentProviderConfigEditor.php', @@ -11805,7 +11812,8 @@ phutil_register_library_map(array( 'PhortuneAccountOrdersController' => 'PhortuneAccountProfileController', 'PhortuneAccountOverviewController' => 'PhortuneAccountProfileController', 'PhortuneAccountPHIDType' => 'PhabricatorPHIDType', - 'PhortuneAccountPaymentMethodsController' => 'PhortuneAccountProfileController', + 'PhortuneAccountPaymentMethodListController' => 'PhortuneAccountProfileController', + 'PhortuneAccountPaymentMethodViewController' => 'PhortuneAccountController', 'PhortuneAccountProfileController' => 'PhortuneAccountController', 'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneAccountSubscriptionController' => 'PhortuneAccountProfileController', @@ -11896,13 +11904,20 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorExtendedPolicyInterface', 'PhabricatorPolicyCodexInterface', + 'PhabricatorApplicationTransactionInterface', ), 'PhortunePaymentMethodCreateController' => 'PhortuneController', 'PhortunePaymentMethodDisableController' => 'PhortuneController', 'PhortunePaymentMethodEditController' => 'PhortuneController', + 'PhortunePaymentMethodEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhortunePaymentMethodNameTransaction' => 'PhortunePaymentMethodTransactionType', 'PhortunePaymentMethodPHIDType' => 'PhabricatorPHIDType', 'PhortunePaymentMethodPolicyCodex' => 'PhabricatorPolicyCodex', 'PhortunePaymentMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhortunePaymentMethodStatusTransaction' => 'PhortunePaymentMethodTransactionType', + 'PhortunePaymentMethodTransaction' => 'PhabricatorModularTransaction', + 'PhortunePaymentMethodTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhortunePaymentMethodTransactionType' => 'PhabricatorModularTransactionType', 'PhortunePaymentProvider' => 'Phobject', 'PhortunePaymentProviderConfig' => array( 'PhortuneDAO', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index fbabe97f35..8ec159d024 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -72,7 +72,10 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { '(?P\d+)/' => array( 'details/' => 'PhortuneAccountDetailsController', - 'methods/' => 'PhortuneAccountPaymentMethodsController', + 'methods/' => array( + '' => 'PhortuneAccountPaymentMethodListController', + '(?P\d+)/' => 'PhortuneAccountPaymentMethodViewController', + ), 'orders/' => 'PhortuneAccountOrdersController', 'charges/' => 'PhortuneAccountChargesController', 'subscriptions/' => 'PhortuneAccountSubscriptionController', diff --git a/src/applications/phortune/controller/account/PhortuneAccountController.php b/src/applications/phortune/controller/account/PhortuneAccountController.php index ed48c67383..8b1a44a81a 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountController.php @@ -23,6 +23,10 @@ abstract class PhortuneAccountController abstract protected function shouldRequireAccountEditCapability(); abstract protected function handleAccountRequest(AphrontRequest $request); + private function hasAccount() { + return (bool)$this->account; + } + final protected function getAccount() { if ($this->account === null) { throw new Exception( @@ -37,8 +41,10 @@ abstract class PhortuneAccountController protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $account = $this->getAccount(); - if ($account) { + // If we hit a policy exception, we can make it here without finding + // an account. + if ($this->hasAccount()) { + $account = $this->getAccount(); $crumbs->addTextCrumb($account->getName(), $account->getURI()); } diff --git a/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php similarity index 63% rename from src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php rename to src/applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php index ec10205451..5fa7cdf4e6 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php @@ -1,6 +1,6 @@ getID(); - // TODO: Allow adding a card here directly $add = id(new PHUIButtonView()) ->setTag('a') - ->setText(pht('New Payment Method')) + ->setText(pht('Add Payment Method')) ->setIcon('fa-plus') - ->setHref($this->getApplicationURI("{$id}/card/new/")); + ->setHref($this->getApplicationURI("{$id}/card/new/")) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Payment Methods')); + ->setHeader(pht('Payment Methods')) + ->addActionLink($add); $list = id(new PHUIObjectItemListView()) ->setUser($viewer) @@ -74,39 +76,14 @@ final class PhortuneAccountPaymentMethodsController foreach ($methods as $method) { $id = $method->getID(); - $item = new PHUIObjectItemView(); - $item->setHeader($method->getFullDisplayName()); - - switch ($method->getStatus()) { - case PhortunePaymentMethod::STATUS_ACTIVE: - $item->setStatusIcon('fa-check green'); - - $disable_uri = $this->getApplicationURI('card/'.$id.'/disable/'); - $item->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-times') - ->setHref($disable_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(true)); - break; - case PhortunePaymentMethod::STATUS_DISABLED: - $item->setStatusIcon('fa-ban lightbluetext'); - $item->setDisabled(true); - break; - } + $item = id(new PHUIObjectItemView()) + ->setObjectName($method->getObjectName()) + ->setHeader($method->getFullDisplayName()) + ->setHref($method->getURI()); $provider = $method->buildPaymentProvider(); $item->addAttribute($provider->getPaymentMethodProviderDescription()); - $edit_uri = $this->getApplicationURI('card/'.$id.'/edit/'); - - $item->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-pencil') - ->setHref($edit_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - $list->addItem($item); } diff --git a/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodViewController.php b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodViewController.php new file mode 100644 index 0000000000..00c7e9798e --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodViewController.php @@ -0,0 +1,154 @@ +getViewer(); + $account = $this->getAccount(); + + $method = id(new PhortunePaymentMethodQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->withIDs(array($request->getURIData('id'))) + ->withStatuses( + array( + PhortunePaymentMethod::STATUS_ACTIVE, + )) + ->executeOne(); + if (!$method) { + return new Aphront404Response(); + } + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Payment Methods'), $account->getPaymentMethodsURI()) + ->addTextCrumb($method->getObjectName()) + ->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader($method->getFullDisplayName()); + + $details = $this->newDetailsView($method); + + $timeline = $this->buildTransactionTimeline( + $method, + new PhortunePaymentMethodTransactionQuery()); + $timeline->setShouldTerminate(true); + + $autopay = $this->newAutopayView($method); + + $curtain = $this->buildCurtainView($method); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn( + array( + $details, + $autopay, + $timeline, + )); + + return $this->newPage() + ->setTitle($method->getObjectName()) + ->setCrumbs($crumbs) + ->appendChild($view); + } + + private function buildCurtainView(PhortunePaymentMethod $method) { + $viewer = $this->getViewer(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $method, + PhabricatorPolicyCapability::CAN_EDIT); + + $edit_uri = $this->getApplicationURI( + urisprintf( + 'card/%d/edit/', + $method->getID())); + + $remove_uri = $this->getApplicationURI( + urisprintf( + 'card/%d/disable/', + $method->getID())); + + $curtain = $this->newCurtainView($method); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Payment Method')) + ->setIcon('fa-pencil') + ->setHref($edit_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Remove Payment Method')) + ->setIcon('fa-times') + ->setHref($remove_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); + + return $curtain; + } + + private function newDetailsView(PhortunePaymentMethod $method) { + $viewer = $this->getViewer(); + + $merchant_phid = $method->getMerchantPHID(); + $handles = $viewer->loadHandles( + array( + $merchant_phid, + )); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + if (strlen($method->getName())) { + $view->addProperty(pht('Name'), $method->getDisplayName()); + } + + $view->addProperty(pht('Summary'), $method->getSummary()); + $view->addProperty(pht('Expires'), $method->getDisplayExpires()); + + $view->addProperty( + pht('Merchant'), + $handles[$merchant_phid]->renderLink()); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Payment Method Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addPropertyList($view); + } + + private function newAutopayView(PhortunePaymentMethod $method) { + $viewer = $this->getViewer(); + + $subscriptions = id(new PhortuneSubscriptionQuery()) + ->setViewer($viewer) + ->withPaymentMethodPHIDs(array($method->getPHID())) + ->execute(); + + $table = id(new PhortuneSubscriptionTableView()) + ->setViewer($viewer) + ->setSubscriptions($subscriptions) + ->newTableView(); + + $table->setNoDataString( + pht( + 'This payment method is not the default payment method for '. + 'any subscriptions.')); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Autopay Subscriptions')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php index 3acefffd6c..779721c4f3 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php @@ -47,11 +47,8 @@ final class PhortuneAccountSubscriptionController ->setLimit(25) ->execute(); - $handles = $this->loadViewerHandles(mpull($subscriptions, 'getPHID')); - $table = id(new PhortuneSubscriptionTableView()) ->setUser($viewer) - ->setHandles($handles) ->setSubscriptions($subscriptions); $header = id(new PHUIHeaderView()) diff --git a/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php b/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php deleted file mode 100644 index c068862631..0000000000 --- a/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php +++ /dev/null @@ -1,303 +0,0 @@ -getViewer(); - $account_id = $request->getURIData('accountID'); - - $account = id(new PhortuneAccountQuery()) - ->setViewer($viewer) - ->withIDs(array($account_id)) - ->executeOne(); - if (!$account) { - return new Aphront404Response(); - } - $account_id = $account->getID(); - - $merchant = id(new PhortuneMerchantQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getInt('merchantID'))) - ->executeOne(); - if (!$merchant) { - return new Aphront404Response(); - } - - $cart_id = $request->getInt('cartID'); - $subscription_id = $request->getInt('subscriptionID'); - if ($cart_id) { - $cancel_uri = $this->getApplicationURI("cart/{$cart_id}/checkout/"); - } else if ($subscription_id) { - $cancel_uri = $this->getApplicationURI( - "{$account_id}/subscription/edit/{$subscription_id}/"); - } else { - $cancel_uri = $this->getApplicationURI($account->getID().'/'); - } - - $providers = $this->loadCreatePaymentMethodProvidersForMerchant($merchant); - if (!$providers) { - throw new Exception( - pht( - 'There are no payment providers enabled that can add payment '. - 'methods.')); - } - - if (count($providers) == 1) { - // If there's only one provider, always choose it. - $provider_id = head_key($providers); - } else { - $provider_id = $request->getInt('providerID'); - if (empty($providers[$provider_id])) { - $choices = array(); - foreach ($providers as $provider) { - $choices[] = $this->renderSelectProvider($provider); - } - - $content = phutil_tag( - 'div', - array( - 'class' => 'phortune-payment-method-list', - ), - $choices); - - return $this->newDialog() - ->setRenderDialogAsDiv(true) - ->setTitle(pht('Add Payment Method')) - ->appendParagraph(pht('Choose a payment method to add:')) - ->appendChild($content) - ->addCancelButton($cancel_uri); - } - } - - $provider = $providers[$provider_id]; - - $errors = array(); - $display_exception = null; - if ($request->isFormPost() && $request->getBool('isProviderForm')) { - $method = id(new PhortunePaymentMethod()) - ->setAccountPHID($account->getPHID()) - ->setAuthorPHID($viewer->getPHID()) - ->setMerchantPHID($merchant->getPHID()) - ->setProviderPHID($provider->getProviderConfig()->getPHID()) - ->setStatus(PhortunePaymentMethod::STATUS_ACTIVE); - - // Limit the rate at which you can attempt to add payment methods. This - // is intended as a line of defense against using Phortune to validate a - // large list of stolen credit card numbers. - - PhabricatorSystemActionEngine::willTakeAction( - array($viewer->getPHID()), - new PhortuneAddPaymentMethodAction(), - 1); - - if (!$errors) { - $errors = $this->processClientErrors( - $provider, - $request->getStr('errors')); - } - - if (!$errors) { - $client_token_raw = $request->getStr('token'); - $client_token = null; - try { - $client_token = phutil_json_decode($client_token_raw); - } catch (PhutilJSONParserException $ex) { - $errors[] = pht( - 'There was an error decoding token information submitted by the '. - 'client. Expected a JSON-encoded token dictionary, received: %s.', - nonempty($client_token_raw, pht('nothing'))); - } - - if (!$provider->validateCreatePaymentMethodToken($client_token)) { - $errors[] = pht( - 'There was an error with the payment token submitted by the '. - 'client. Expected a valid dictionary, received: %s.', - $client_token_raw); - } - - if (!$errors) { - try { - $provider->createPaymentMethodFromRequest( - $request, - $method, - $client_token); - } catch (PhortuneDisplayException $exception) { - $display_exception = $exception; - } catch (Exception $ex) { - $errors = array( - pht('There was an error adding this payment method:'), - $ex->getMessage(), - ); - } - } - } - - if (!$errors && !$display_exception) { - $method->save(); - - // If we added this method on a cart flow, return to the cart to - // check out. - if ($cart_id) { - $next_uri = $this->getApplicationURI( - "cart/{$cart_id}/checkout/?paymentMethodID=".$method->getID()); - } else if ($subscription_id) { - $next_uri = new PhutilURI($cancel_uri); - $next_uri->replaceQueryParam('added', true); - } else { - $account_uri = $this->getApplicationURI($account->getID().'/'); - $next_uri = new PhutilURI($account_uri); - $next_uri->setFragment('payment'); - } - - return id(new AphrontRedirectResponse())->setURI($next_uri); - } else { - if ($display_exception) { - $dialog_body = $display_exception->getView(); - } else { - $dialog_body = id(new PHUIInfoView()) - ->setErrors($errors); - } - - return $this->newDialog() - ->setTitle(pht('Error Adding Payment Method')) - ->appendChild($dialog_body) - ->addCancelButton($request->getRequestURI()); - } - } - - $form = $provider->renderCreatePaymentMethodForm($request, $errors); - - $form - ->setUser($viewer) - ->setAction($request->getRequestURI()) - ->setWorkflow(true) - ->addHiddenInput('providerID', $provider_id) - ->addHiddenInput('cartID', $request->getInt('cartID')) - ->addHiddenInput('subscriptionID', $request->getInt('subscriptionID')) - ->addHiddenInput('isProviderForm', true) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Add Payment Method')) - ->addCancelButton($cancel_uri)); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Method')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Add Payment Method')); - $crumbs->setBorder(true); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Add Payment Method')) - ->setHeaderIcon('fa-plus-square'); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $box, - )); - - return $this->newPage() - ->setTitle($provider->getPaymentMethodDescription()) - ->setCrumbs($crumbs) - ->appendChild($view); - - } - - private function renderSelectProvider( - PhortunePaymentProvider $provider) { - - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $description = $provider->getPaymentMethodDescription(); - $icon_uri = $provider->getPaymentMethodIcon(); - $details = $provider->getPaymentMethodProviderDescription(); - - $this->requireResource('phortune-css'); - - $icon = id(new PHUIIconView()) - ->setSpriteSheet(PHUIIconView::SPRITE_LOGIN) - ->setSpriteIcon($provider->getPaymentMethodIcon()); - - $button = id(new PHUIButtonView()) - ->setSize(PHUIButtonView::BIG) - ->setColor(PHUIButtonView::GREY) - ->setIcon($icon) - ->setText($description) - ->setSubtext($details) - ->setMetadata(array('disableWorkflow' => true)); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->setAction($request->getRequestURI()) - ->addHiddenInput('providerID', $provider->getProviderConfig()->getID()) - ->appendChild($button); - - return $form; - } - - private function processClientErrors( - PhortunePaymentProvider $provider, - $client_errors_raw) { - - $errors = array(); - - $client_errors = null; - try { - $client_errors = phutil_json_decode($client_errors_raw); - } catch (PhutilJSONParserException $ex) { - $errors[] = pht( - 'There was an error decoding error information submitted by the '. - 'client. Expected a JSON-encoded list of error codes, received: %s.', - nonempty($client_errors_raw, pht('nothing'))); - } - - foreach (array_unique($client_errors) as $key => $client_error) { - $client_errors[$key] = $provider->translateCreatePaymentMethodErrorCode( - $client_error); - } - - foreach (array_unique($client_errors) as $client_error) { - switch ($client_error) { - case PhortuneErrCode::ERR_CC_INVALID_NUMBER: - $message = pht( - 'The card number you entered is not a valid card number. Check '. - 'that you entered it correctly.'); - break; - case PhortuneErrCode::ERR_CC_INVALID_CVC: - $message = pht( - 'The CVC code you entered is not a valid CVC code. Check that '. - 'you entered it correctly. The CVC code is a 3-digit or 4-digit '. - 'numeric code which usually appears on the back of the card.'); - break; - case PhortuneErrCode::ERR_CC_INVALID_EXPIRY: - $message = pht( - 'The card expiration date is not a valid expiration date. Check '. - 'that you entered it correctly. You can not add an expired card '. - 'as a payment method.'); - break; - default: - $message = $provider->getCreatePaymentMethodErrorMessage( - $client_error); - if (!$message) { - $message = pht( - "There was an unexpected error ('%s') processing payment ". - "information.", - $client_error); - - phlog($message); - } - break; - } - - $errors[$client_error] = $message; - } - - return $errors; - } - -} diff --git a/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodCreateController.php b/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodCreateController.php new file mode 100644 index 0000000000..d32cb70f37 --- /dev/null +++ b/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodCreateController.php @@ -0,0 +1,462 @@ +getViewer(); + + $account_id = $request->getURIData('accountID'); + $account = id(new PhortuneAccountQuery()) + ->setViewer($viewer) + ->withIDs(array($account_id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$account) { + return new Aphront404Response(); + } + + $cart_id = $request->getInt('cartID'); + $subscription_id = $request->getInt('subscriptionID'); + $merchant_id = $request->getInt('merchantID'); + + if ($cart_id) { + $cart = id(new PhortuneCartQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->withIDs(array($cart_id)) + ->executeOne(); + if (!$cart) { + return new Aphront404Response(); + } + + $subscription_phid = $cart->getSubscriptionPHID(); + if ($subscription_phid) { + $subscription = id(new PhortuneSubscriptionQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->withPHIDs(array($subscription_phid)) + ->executeOne(); + if (!$subscription) { + return new Aphront404Response(); + } + } else { + $subscription = null; + } + + $merchant = $cart->getMerchant(); + + $cart_id = $cart->getID(); + $subscription_id = null; + $merchant_id = null; + + $next_uri = $cart->getCheckoutURI(); + } else if ($subscription_id) { + $subscription = id(new PhortuneSubscriptionQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->withIDs(array($subscription_id)) + ->executeOne(); + if (!$subscription) { + return new Aphront404Response(); + } + + $cart = null; + $merchant = $subscription->getMerchant(); + + $cart_id = null; + $subscription_id = $subscription->getID(); + $merchant_id = null; + + $next_uri = $subscription->getURI(); + } else if ($merchant_id) { + $merchant_phids = $account->getMerchantPHIDs(); + if ($merchant_phids) { + $merchant = id(new PhortuneMerchantQuery()) + ->setViewer($viewer) + ->withIDs(array($merchant_id)) + ->withPHIDs($merchant_phids) + ->executeOne(); + } else { + $merchant = null; + } + + if (!$merchant) { + return new Aphront404Response(); + } + + $cart = null; + $subscription = null; + + $cart_id = null; + $subscription_id = null; + $merchant_id = $merchant->getID(); + + $next_uri = $account->getPaymentMethodsURI(); + } else { + $next_uri = $account->getPaymentMethodsURI(); + + $merchant_phids = $account->getMerchantPHIDs(); + if ($merchant_phids) { + $merchants = id(new PhortuneMerchantQuery()) + ->setViewer($viewer) + ->withPHIDs($merchant_phids) + ->needProfileImage(true) + ->execute(); + } else { + $merchants = array(); + } + + if (!$merchants) { + return $this->newDialog() + ->setTitle(pht('No Merchants')) + ->appendParagraph( + pht( + 'You have not established a relationship with any merchants '. + 'yet. Create an order or subscription before adding payment '. + 'methods.')) + ->addCancelButton($next_uri); + } + + // If there's more than one merchant, ask the user to pick which one they + // want to pay. If there's only one, just pick it for them. + if (count($merchants) > 1) { + $menu = $this->newMerchantMenu($merchants); + + $form = id(new AphrontFormView()) + ->appendInstructions( + pht( + 'Choose the merchant you want to pay.')); + + return $this->newDialog() + ->setTitle(pht('Choose a Merchant')) + ->appendForm($form) + ->appendChild($menu) + ->addCancelButton($next_uri); + } + + $cart = null; + $subscription = null; + $merchant = head($merchants); + + $cart_id = null; + $subscription_id = null; + $merchant_id = $merchant->getID(); + } + + $providers = $this->loadCreatePaymentMethodProvidersForMerchant($merchant); + if (!$providers) { + throw new Exception( + pht( + 'There are no payment providers enabled that can add payment '. + 'methods.')); + } + + $state_params = array( + 'cartID' => $cart_id, + 'subscriptionID' => $subscription_id, + 'merchantID' => $merchant_id, + ); + $state_params = array_filter($state_params); + + $state_uri = new PhutilURI($request->getRequestURI()); + foreach ($state_params as $key => $value) { + $state_uri->replaceQueryParam($key, $value); + } + + $provider_id = $request->getInt('providerID'); + if (isset($providers[$provider_id])) { + $provider = $providers[$provider_id]; + } else { + // If there's more than one provider, ask the user to pick how they + // want to pay. If there's only one, just pick it. + if (count($providers) > 1) { + $menu = $this->newProviderMenu($providers, $state_uri); + + return $this->newDialog() + ->setTitle(pht('Choose a Payment Method')) + ->appendChild($menu) + ->addCancelButton($next_uri); + } + + $provider = head($providers); + } + + $provider_id = $provider->getProviderConfig()->getID(); + + $state_params['providerID'] = $provider_id; + + $errors = array(); + $display_exception = null; + if ($request->isFormPost() && $request->getBool('isProviderForm')) { + $method = id(new PhortunePaymentMethod()) + ->setAccountPHID($account->getPHID()) + ->setAuthorPHID($viewer->getPHID()) + ->setMerchantPHID($merchant->getPHID()) + ->setProviderPHID($provider->getProviderConfig()->getPHID()) + ->setStatus(PhortunePaymentMethod::STATUS_ACTIVE); + + // Limit the rate at which you can attempt to add payment methods. This + // is intended as a line of defense against using Phortune to validate a + // large list of stolen credit card numbers. + + PhabricatorSystemActionEngine::willTakeAction( + array($viewer->getPHID()), + new PhortuneAddPaymentMethodAction(), + 1); + + if (!$errors) { + $errors = $this->processClientErrors( + $provider, + $request->getStr('errors')); + } + + if (!$errors) { + $client_token_raw = $request->getStr('token'); + $client_token = null; + try { + $client_token = phutil_json_decode($client_token_raw); + } catch (PhutilJSONParserException $ex) { + $errors[] = pht( + 'There was an error decoding token information submitted by the '. + 'client. Expected a JSON-encoded token dictionary, received: %s.', + nonempty($client_token_raw, pht('nothing'))); + } + + if (!$provider->validateCreatePaymentMethodToken($client_token)) { + $errors[] = pht( + 'There was an error with the payment token submitted by the '. + 'client. Expected a valid dictionary, received: %s.', + $client_token_raw); + } + + if (!$errors) { + try { + $provider->createPaymentMethodFromRequest( + $request, + $method, + $client_token); + } catch (PhortuneDisplayException $exception) { + $display_exception = $exception; + } catch (Exception $ex) { + $errors = array( + pht('There was an error adding this payment method:'), + $ex->getMessage(), + ); + } + } + } + + if (!$errors && !$display_exception) { + $xactions = array(); + + $xactions[] = $method->getApplicationTransactionTemplate() + ->setTransactionType(PhabricatorTransactions::TYPE_CREATE) + ->setNewValue(true); + + $editor = id(new PhortunePaymentMethodEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($method, $xactions); + + $next_uri = new PhutilURI($next_uri); + + // If we added this method on a cart flow, return to the cart to + // checkout with this payment method selected. + if ($cart_id) { + $next_uri->replaceQueryParam('paymentMethodID', $method->getID()); + } + + return id(new AphrontRedirectResponse())->setURI($next_uri); + } else { + if ($display_exception) { + $dialog_body = $display_exception->getView(); + } else { + $dialog_body = id(new PHUIInfoView()) + ->setErrors($errors); + } + + return $this->newDialog() + ->setTitle(pht('Error Adding Payment Method')) + ->appendChild($dialog_body) + ->addCancelButton($request->getRequestURI()); + } + } + + $form = $provider->renderCreatePaymentMethodForm($request, $errors); + + $form + ->setViewer($viewer) + ->setAction($request->getPath()) + ->setWorkflow(true) + ->addHiddenInput('isProviderForm', true) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Add Payment Method')) + ->addCancelButton($next_uri)); + + foreach ($state_params as $key => $value) { + $form->addHiddenInput($key, $value); + } + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Method')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Add Payment Method')) + ->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Add Payment Method')) + ->setHeaderIcon('fa-plus-square'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter( + array( + $box, + )); + + return $this->newPage() + ->setTitle($provider->getPaymentMethodDescription()) + ->setCrumbs($crumbs) + ->appendChild($view); + + } + + private function processClientErrors( + PhortunePaymentProvider $provider, + $client_errors_raw) { + + $errors = array(); + + $client_errors = null; + try { + $client_errors = phutil_json_decode($client_errors_raw); + } catch (PhutilJSONParserException $ex) { + $errors[] = pht( + 'There was an error decoding error information submitted by the '. + 'client. Expected a JSON-encoded list of error codes, received: %s.', + nonempty($client_errors_raw, pht('nothing'))); + } + + foreach (array_unique($client_errors) as $key => $client_error) { + $client_errors[$key] = $provider->translateCreatePaymentMethodErrorCode( + $client_error); + } + + foreach (array_unique($client_errors) as $client_error) { + switch ($client_error) { + case PhortuneErrCode::ERR_CC_INVALID_NUMBER: + $message = pht( + 'The card number you entered is not a valid card number. Check '. + 'that you entered it correctly.'); + break; + case PhortuneErrCode::ERR_CC_INVALID_CVC: + $message = pht( + 'The CVC code you entered is not a valid CVC code. Check that '. + 'you entered it correctly. The CVC code is a 3-digit or 4-digit '. + 'numeric code which usually appears on the back of the card.'); + break; + case PhortuneErrCode::ERR_CC_INVALID_EXPIRY: + $message = pht( + 'The card expiration date is not a valid expiration date. Check '. + 'that you entered it correctly. You can not add an expired card '. + 'as a payment method.'); + break; + default: + $message = $provider->getCreatePaymentMethodErrorMessage( + $client_error); + if (!$message) { + $message = pht( + "There was an unexpected error ('%s') processing payment ". + "information.", + $client_error); + + phlog($message); + } + break; + } + + $errors[$client_error] = $message; + } + + return $errors; + } + + private function newMerchantMenu(array $merchants) { + assert_instances_of($merchants, 'PhortuneMerchant'); + + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $menu = id(new PHUIObjectItemListView()) + ->setUser($viewer) + ->setBig(true) + ->setFlush(true); + + foreach ($merchants as $merchant) { + $merchant_uri = id(new PhutilURI($request->getRequestURI())) + ->replaceQueryParam('merchantID', $merchant->getID()); + + $item = id(new PHUIObjectItemView()) + ->setObjectName($merchant->getObjectName()) + ->setHeader($merchant->getName()) + ->setHref($merchant_uri) + ->setClickable(true) + ->setImageURI($merchant->getProfileImageURI()); + + $menu->addItem($item); + } + + return $menu; + } + + private function newProviderMenu(array $providers, PhutilURI $state_uri) { + assert_instances_of($providers, 'PhortunePaymentProvider'); + + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $menu = id(new PHUIObjectItemListView()) + ->setUser($viewer) + ->setBig(true) + ->setFlush(true); + + foreach ($providers as $provider) { + $provider_id = $provider->getProviderConfig()->getID(); + + $provider_uri = id(clone $state_uri) + ->replaceQueryParam('providerID', $provider_id); + + $description = $provider->getPaymentMethodDescription(); + $icon_uri = $provider->getPaymentMethodIcon(); + $details = $provider->getPaymentMethodProviderDescription(); + + $icon = id(new PHUIIconView()) + ->setSpriteSheet(PHUIIconView::SPRITE_LOGIN) + ->setSpriteIcon($icon_uri); + + $item = id(new PHUIObjectItemView()) + ->setHeader($description) + ->setHref($provider_uri) + ->setClickable(true) + ->addAttribute($details) + ->setImageIcon($icon); + + $menu->addItem($item); + } + + return $menu; + } + +} diff --git a/src/applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php b/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php similarity index 71% rename from src/applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php rename to src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php index f5feec8a29..46d75ffc04 100644 --- a/src/applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php +++ b/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php @@ -26,14 +26,23 @@ final class PhortunePaymentMethodDisableController $account = $method->getAccount(); $account_id = $account->getID(); - $account_uri = $this->getApplicationURI("/account/billing/{$account_id}/"); + $account_uri = $account->getPaymentMethodsURI(); if ($request->isFormPost()) { + $xactions = array(); - // TODO: ApplicationTransactions!!!! - $method - ->setStatus(PhortunePaymentMethod::STATUS_DISABLED) - ->save(); + $xactions[] = $method->getApplicationTransactionTemplate() + ->setTransactionType( + PhortunePaymentMethodStatusTransaction::TRANSACTIONTYPE) + ->setNewValue(PhortunePaymentMethod::STATUS_DISABLED); + + $editor = id(new PhortunePaymentMethodEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($method, $xactions); return id(new AphrontRedirectResponse())->setURI($account_uri); } diff --git a/src/applications/phortune/controller/payment/PhortunePaymentMethodEditController.php b/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodEditController.php similarity index 62% rename from src/applications/phortune/controller/payment/PhortunePaymentMethodEditController.php rename to src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodEditController.php index dc23b81ad0..349f08d319 100644 --- a/src/applications/phortune/controller/payment/PhortunePaymentMethodEditController.php +++ b/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodEditController.php @@ -20,25 +20,36 @@ final class PhortunePaymentMethodEditController return new Aphront404Response(); } + $next_uri = $method->getURI(); + $account = $method->getAccount(); - $account_uri = $this->getApplicationURI($account->getID().'/'); + $v_name = $method->getName(); if ($request->isFormPost()) { + $v_name = $request->getStr('name'); - $name = $request->getStr('name'); + $xactions = array(); - // TODO: Use ApplicationTransactions + $xactions[] = $method->getApplicationTransactionTemplate() + ->setTransactionType( + PhortunePaymentMethodNameTransaction::TRANSACTIONTYPE) + ->setNewValue($v_name); - $method->setName($name); - $method->save(); + $editor = id(new PhortunePaymentMethodEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); - return id(new AphrontRedirectResponse())->setURI($account_uri); + $editor->applyTransactions($method, $xactions); + + return id(new AphrontRedirectResponse())->setURI($next_uri); } $provider = $method->buildPaymentProvider(); $form = id(new AphrontFormView()) - ->setUser($viewer) + ->setViewer($viewer) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) @@ -54,7 +65,7 @@ final class PhortunePaymentMethodEditController ->setValue($method->getDisplayExpires())) ->appendChild( id(new AphrontFormSubmitControl()) - ->addCancelButton($account_uri) + ->addCancelButton($next_uri) ->setValue(pht('Save Changes'))); $box = id(new PHUIObjectBoxView()) @@ -62,11 +73,12 @@ final class PhortunePaymentMethodEditController ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($account->getName(), $account_uri); - $crumbs->addTextCrumb($method->getDisplayName()); - $crumbs->addTextCrumb(pht('Edit')); - $crumbs->setBorder(true); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($account->getName(), $account->getURI()) + ->addTextCrumb(pht('Payment Methods'), $account->getPaymentMethodsURI()) + ->addTextCrumb($method->getObjectName(), $method->getURI()) + ->addTextCrumb(pht('Edit')) + ->setBorder(true); $header = id(new PHUIHeaderView()) ->setHeader(pht('Edit Payment Method')) @@ -74,15 +86,15 @@ final class PhortunePaymentMethodEditController $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFooter(array( - $box, - )); + ->setFooter( + array( + $box, + )); return $this->newPage() ->setTitle(pht('Edit Payment Method')) ->setCrumbs($crumbs) ->appendChild($view); - } } diff --git a/src/applications/phortune/editor/PhortunePaymentMethodEditor.php b/src/applications/phortune/editor/PhortunePaymentMethodEditor.php new file mode 100644 index 0000000000..4b6c8cedb3 --- /dev/null +++ b/src/applications/phortune/editor/PhortunePaymentMethodEditor.php @@ -0,0 +1,18 @@ +paymentMethodPHIDs = $method_phids; + return $this; + } + public function needTriggers($need_triggers) { $this->needTriggers = $need_triggers; return $this; } + public function newResultObject() { + return new PhortuneSubscription(); + } + protected function loadPage() { - $table = new PhortuneSubscription(); - $conn = $table->establishConnection('r'); - - $rows = queryfx_all( - $conn, - 'SELECT subscription.* FROM %T subscription %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn), - $this->buildOrderClause($conn), - $this->buildLimitClause($conn)); - - return $table->loadAllFromArray($rows); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $subscriptions) { @@ -67,6 +66,7 @@ final class PhortuneSubscriptionQuery $account = idx($accounts, $subscription->getAccountPHID()); if (!$account) { unset($subscriptions[$key]); + $this->didRejectResult($subscription); continue; } $subscription->attachAccount($account); @@ -86,6 +86,7 @@ final class PhortuneSubscriptionQuery $merchant = idx($merchants, $subscription->getMerchantPHID()); if (!$merchant) { unset($subscriptions[$key]); + $this->didRejectResult($subscription); continue; } $subscription->attachMerchant($merchant); @@ -112,6 +113,7 @@ final class PhortuneSubscriptionQuery $implementation = idx($implementations, $ref); if (!$implementation) { unset($subscriptions[$key]); + $this->didRejectResult($subscription); continue; } $subscription->attachImplementation($implementation); @@ -133,6 +135,7 @@ final class PhortuneSubscriptionQuery $trigger = idx($triggers, $subscription->getTriggerPHID()); if (!$trigger) { unset($subscriptions[$key]); + $this->didRejectResult($subscription); continue; } $subscription->attachTrigger($trigger); @@ -142,10 +145,8 @@ final class PhortuneSubscriptionQuery return $subscriptions; } - protected function buildWhereClause(AphrontDatabaseConnection $conn) { - $where = array(); - - $where[] = $this->buildPagingClause($conn); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( @@ -182,7 +183,18 @@ final class PhortuneSubscriptionQuery $this->statuses); } - return $this->formatWhereClause($conn, $where); + if ($this->paymentMethodPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'subscription.defaultPaymentMethodPHID IN (%Ls)', + $this->paymentMethodPHIDs); + } + + return $where; + } + + protected function getPrimaryTableAlias() { + return 'subscription'; } public function getQueryApplicationClass() { diff --git a/src/applications/phortune/query/PhortuneSubscriptionSearchEngine.php b/src/applications/phortune/query/PhortuneSubscriptionSearchEngine.php index 62d4f79a25..3f4bfb4e84 100644 --- a/src/applications/phortune/query/PhortuneSubscriptionSearchEngine.php +++ b/src/applications/phortune/query/PhortuneSubscriptionSearchEngine.php @@ -125,18 +125,6 @@ final class PhortuneSubscriptionSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } - protected function getRequiredHandlePHIDsForResultList( - array $subscriptions, - PhabricatorSavedQuery $query) { - $phids = array(); - foreach ($subscriptions as $subscription) { - $phids[] = $subscription->getPHID(); - $phids[] = $subscription->getMerchantPHID(); - $phids[] = $subscription->getAuthorPHID(); - } - return $phids; - } - protected function renderResultList( array $subscriptions, PhabricatorSavedQuery $query, @@ -147,7 +135,6 @@ final class PhortuneSubscriptionSearchEngine $table = id(new PhortuneSubscriptionTableView()) ->setUser($viewer) - ->setHandles($handles) ->setSubscriptions($subscriptions); $merchant = $this->getMerchant(); diff --git a/src/applications/phortune/storage/PhortuneAccount.php b/src/applications/phortune/storage/PhortuneAccount.php index bf313f37fc..fcb81ceba3 100644 --- a/src/applications/phortune/storage/PhortuneAccount.php +++ b/src/applications/phortune/storage/PhortuneAccount.php @@ -117,6 +117,12 @@ final class PhortuneAccount extends PhortuneDAO $this->getID()); } + public function getPaymentMethodsURI() { + return urisprintf( + '/phortune/account/%d/methods/', + $this->getID()); + } + public function attachMerchantPHIDs(array $merchant_phids) { $this->merchantPHIDs = $merchant_phids; return $this; diff --git a/src/applications/phortune/storage/PhortuneMerchant.php b/src/applications/phortune/storage/PhortuneMerchant.php index 4916cfede7..830ff8e1d5 100644 --- a/src/applications/phortune/storage/PhortuneMerchant.php +++ b/src/applications/phortune/storage/PhortuneMerchant.php @@ -70,6 +70,10 @@ final class PhortuneMerchant extends PhortuneDAO return $this->assertAttached($this->profileImageFile); } + public function getObjectName() { + return pht('Merchant %d', $this->getID()); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/phortune/storage/PhortunePaymentMethod.php b/src/applications/phortune/storage/PhortunePaymentMethod.php index 9aa88c1cf3..f04621c020 100644 --- a/src/applications/phortune/storage/PhortunePaymentMethod.php +++ b/src/applications/phortune/storage/PhortunePaymentMethod.php @@ -9,7 +9,8 @@ final class PhortunePaymentMethod implements PhabricatorPolicyInterface, PhabricatorExtendedPolicyInterface, - PhabricatorPolicyCodexInterface { + PhabricatorPolicyCodexInterface, + PhabricatorApplicationTransactionInterface { const STATUS_ACTIVE = 'payment:active'; const STATUS_DISABLED = 'payment:disabled'; @@ -140,6 +141,29 @@ final class PhortunePaymentMethod return ($this->getStatus() === self::STATUS_ACTIVE); } + public function getURI() { + return urisprintf( + '/phortune/account/%d/methods/%d/', + $this->getAccount()->getID(), + $this->getID()); + } + + public function getObjectName() { + return pht('Payment Method %d', $this->getID()); + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhortunePaymentMethodEditor(); + } + + public function getApplicationTransactionTemplate() { + return new PhortunePaymentMethodTransaction(); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/phortune/storage/PhortunePaymentMethodTransaction.php b/src/applications/phortune/storage/PhortunePaymentMethodTransaction.php new file mode 100644 index 0000000000..16ba306d90 --- /dev/null +++ b/src/applications/phortune/storage/PhortunePaymentMethodTransaction.php @@ -0,0 +1,18 @@ +handles = $handles; - return $this; - } - - public function getHandles() { - return $this->handles; - } - public function setSubscriptions(array $subscriptions) { $this->subscriptions = $subscriptions; return $this; @@ -40,9 +30,15 @@ final class PhortuneSubscriptionTableView extends AphrontView { } public function render() { + return $this->newTableView(); + } + + public function newTableView() { $subscriptions = $this->getSubscriptions(); - $handles = $this->getHandles(); - $viewer = $this->getUser(); + $viewer = $this->getViewer(); + + $phids = mpull($subscriptions, 'getPHID'); + $handles = $viewer->loadHandles($phids); $rows = array(); $rowc = array(); diff --git a/src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodNameTransaction.php b/src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodNameTransaction.php new file mode 100644 index 0000000000..4e25877e15 --- /dev/null +++ b/src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodNameTransaction.php @@ -0,0 +1,39 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + $old_value = $this->getOldValue(); + $new_value = $this->getNewValue(); + + if (strlen($old_value) && strlen($new_value)) { + return pht( + '%s renamed this payment method from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } else if (strlen($new_value)) { + return pht( + '%s set the name of this payment method to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else { + return pht( + '%s removed the name of this payment method (was: %s).', + $this->renderAuthor(), + $this->renderOldValue()); + } + } + +} diff --git a/src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodStatusTransaction.php b/src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodStatusTransaction.php new file mode 100644 index 0000000000..f53c90057d --- /dev/null +++ b/src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodStatusTransaction.php @@ -0,0 +1,22 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + return pht( + '%s changed the status of this payment method.', + $this->renderAuthor()); + } + +} diff --git a/src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodTransactionType.php b/src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodTransactionType.php new file mode 100644 index 0000000000..97c7c3a887 --- /dev/null +++ b/src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodTransactionType.php @@ -0,0 +1,4 @@ + Date: Fri, 16 Aug 2019 14:43:42 -0700 Subject: [PATCH 18/27] When a page throws an exception and response construction throws another exception, throw an aggregate exception Summary: Depends on D20719. Currently, if a page throws an exception (like a policy exception) and rendering that exception into a response (like a policy dialog) throws another exception (for example, while constructing breadcrumbs), we only show the orginal exception. This is usually the more useful exception, but sometimes we actually care about the other exception. Instead of guessing which one is more likely to be useful, throw them both as an "AggregateException" and let the high-level handler flatten it for display. Test Plan: {F6749312} Differential Revision: https://secure.phabricator.com/D20720 --- .../AphrontApplicationConfiguration.php | 14 ++++-- .../AphrontUnhandledExceptionResponse.php | 48 +++++++++++++++++-- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php index a479209125..c24d59ac90 100644 --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -312,11 +312,17 @@ final class AphrontApplicationConfiguration if ($response_exception) { // If we encountered an exception while building a normal response, then // encountered another exception while building a response for the first - // exception, just throw the original exception. It is more likely to be - // useful and point at a root cause than the second exception we ran into - // while telling the user about it. + // exception, throw an aggregate exception that will be unpacked by the + // higher-level handler. This is above our pay grade. if ($original_exception) { - throw $original_exception; + throw new PhutilAggregateException( + pht( + 'Encountered a processing exception, then another exception when '. + 'trying to build a response for the first exception.'), + array( + $response_exception, + $original_exception, + )); } // If we built a response successfully and then ran into an exception diff --git a/src/aphront/response/AphrontUnhandledExceptionResponse.php b/src/aphront/response/AphrontUnhandledExceptionResponse.php index 32d612ca50..2c605cf150 100644 --- a/src/aphront/response/AphrontUnhandledExceptionResponse.php +++ b/src/aphront/response/AphrontUnhandledExceptionResponse.php @@ -61,9 +61,39 @@ final class AphrontUnhandledExceptionResponse return 'unhandled-exception'; } - protected function getResponseBody() { - $ex = $this->exception; + private function getExceptionList() { + return $this->expandException($this->exception); + } + private function expandException($root) { + if ($root instanceof PhutilAggregateException) { + $list = array(); + + $list[] = $root; + + foreach ($root->getExceptions() as $ex) { + foreach ($this->expandException($ex) as $child) { + $list[] = $child; + } + } + + return $list; + } + + return array($root); + } + + protected function getResponseBody() { + $body = array(); + + foreach ($this->getExceptionList() as $ex) { + $body[] = $this->newHTMLMessage($ex); + } + + return $body; + } + + private function newHTMLMessage($ex) { if ($ex instanceof AphrontMalformedRequestException) { $title = $ex->getTitle(); } else { @@ -122,12 +152,20 @@ final class AphrontUnhandledExceptionResponse } protected function buildPlainTextResponseString() { - $ex = $this->exception; + $messages = array(); + foreach ($this->getExceptionList() as $exception) { + $messages[] = $this->newPlainTextMessage($exception); + } + + return implode("\n\n", $messages); + } + + private function newPlainTextMessage($exception) { return pht( '%s: %s', - get_class($ex), - $ex->getMessage()); + get_class($exception), + $exception->getMessage()); } } From a542024b6334a027b16afaf79c0b2263546b09bc Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 16 Aug 2019 16:03:23 -0700 Subject: [PATCH 19/27] Update Phortune subscriptions for modern infrastructure Summary: Depends on D20720. Ref T13366. - Use modern policies and policy interfaces. - Use new merchant authority cache. - Add (some) transactions. - Move MFA from pre-upgrade-gate to post-one-shot-check. - Simplify the autopay workflow. - Use the "reloading arrows" icon for subscriptions more consistently. Test Plan: As a merchant-authority and account-authority, viewed, edited, and changed autopay for subscriptions. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13366 Differential Revision: https://secure.phabricator.com/D20721 --- .../20190816.subscription.01.xaction.sql | 19 + src/__phutil_library_map__.php | 25 +- .../PhabricatorPhortuneApplication.php | 16 +- .../PhortunePaymentMethodPolicyCodex.php | 1 + .../codex/PhortuneSubscriptionPolicyCodex.php | 36 ++ ...hortuneAccountPaymentMethodController.php} | 2 +- ...neAccountSubscriptionAutopayController.php | 137 +++++++ ...rtuneAccountSubscriptionViewController.php | 338 ++++++++++++++++++ ...PhortunePaymentMethodDisableController.php | 41 ++- .../PhortuneSubscriptionViewController.php | 224 ------------ .../editor/PhortuneSubscriptionEditor.php | 18 + .../phid/PhortunePaymentMethodPHIDType.php | 6 +- .../phid/PhortuneSubscriptionPHIDType.php | 8 +- .../PhortuneSubscriptionTransactionQuery.php | 10 + .../storage/PhortunePaymentMethod.php | 1 - .../phortune/storage/PhortuneSubscription.php | 78 ++-- .../PhortuneSubscriptionTransaction.php | 18 + ...PhortuneSubscriptionAutopayTransaction.php | 41 +++ .../PhortuneSubscriptionTransactionType.php | 4 + 19 files changed, 746 insertions(+), 277 deletions(-) create mode 100644 resources/sql/autopatches/20190816.subscription.01.xaction.sql create mode 100644 src/applications/phortune/codex/PhortuneSubscriptionPolicyCodex.php rename src/applications/phortune/controller/account/{PhortuneAccountPaymentMethodListController.php => PhortuneAccountPaymentMethodController.php} (97%) create mode 100644 src/applications/phortune/controller/account/PhortuneAccountSubscriptionAutopayController.php create mode 100644 src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php delete mode 100644 src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php create mode 100644 src/applications/phortune/editor/PhortuneSubscriptionEditor.php create mode 100644 src/applications/phortune/query/PhortuneSubscriptionTransactionQuery.php create mode 100644 src/applications/phortune/storage/PhortuneSubscriptionTransaction.php create mode 100644 src/applications/phortune/xaction/subscription/PhortuneSubscriptionAutopayTransaction.php create mode 100644 src/applications/phortune/xaction/subscription/PhortuneSubscriptionTransactionType.php diff --git a/resources/sql/autopatches/20190816.subscription.01.xaction.sql b/resources/sql/autopatches/20190816.subscription.01.xaction.sql new file mode 100644 index 0000000000..8866ce3a57 --- /dev/null +++ b/resources/sql/autopatches/20190816.subscription.01.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_phortune.phortune_subscriptiontransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) NOT NULL, + oldValue LONGTEXT NOT NULL, + newValue LONGTEXT NOT NULL, + contentSource LONGTEXT NOT NULL, + metadata LONGTEXT NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fbf708ca1d..e1afebccf4 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5249,11 +5249,13 @@ phutil_register_library_map(array( 'PhortuneAccountOrdersController' => 'applications/phortune/controller/account/PhortuneAccountOrdersController.php', 'PhortuneAccountOverviewController' => 'applications/phortune/controller/account/PhortuneAccountOverviewController.php', 'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php', - 'PhortuneAccountPaymentMethodListController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php', + 'PhortuneAccountPaymentMethodController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php', 'PhortuneAccountPaymentMethodViewController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodViewController.php', 'PhortuneAccountProfileController' => 'applications/phortune/controller/account/PhortuneAccountProfileController.php', 'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php', + 'PhortuneAccountSubscriptionAutopayController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionAutopayController.php', 'PhortuneAccountSubscriptionController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionController.php', + 'PhortuneAccountSubscriptionViewController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php', 'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php', 'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php', 'PhortuneAccountTransactionType' => 'applications/phortune/xaction/PhortuneAccountTransactionType.php', @@ -5361,16 +5363,21 @@ phutil_register_library_map(array( 'PhortuneSchemaSpec' => 'applications/phortune/storage/PhortuneSchemaSpec.php', 'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php', 'PhortuneSubscription' => 'applications/phortune/storage/PhortuneSubscription.php', + 'PhortuneSubscriptionAutopayTransaction' => 'applications/phortune/xaction/subscription/PhortuneSubscriptionAutopayTransaction.php', 'PhortuneSubscriptionCart' => 'applications/phortune/cart/PhortuneSubscriptionCart.php', 'PhortuneSubscriptionEditController' => 'applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php', + 'PhortuneSubscriptionEditor' => 'applications/phortune/editor/PhortuneSubscriptionEditor.php', 'PhortuneSubscriptionImplementation' => 'applications/phortune/subscription/PhortuneSubscriptionImplementation.php', 'PhortuneSubscriptionListController' => 'applications/phortune/controller/subscription/PhortuneSubscriptionListController.php', 'PhortuneSubscriptionPHIDType' => 'applications/phortune/phid/PhortuneSubscriptionPHIDType.php', + 'PhortuneSubscriptionPolicyCodex' => 'applications/phortune/codex/PhortuneSubscriptionPolicyCodex.php', 'PhortuneSubscriptionProduct' => 'applications/phortune/product/PhortuneSubscriptionProduct.php', 'PhortuneSubscriptionQuery' => 'applications/phortune/query/PhortuneSubscriptionQuery.php', 'PhortuneSubscriptionSearchEngine' => 'applications/phortune/query/PhortuneSubscriptionSearchEngine.php', 'PhortuneSubscriptionTableView' => 'applications/phortune/view/PhortuneSubscriptionTableView.php', - 'PhortuneSubscriptionViewController' => 'applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php', + 'PhortuneSubscriptionTransaction' => 'applications/phortune/storage/PhortuneSubscriptionTransaction.php', + 'PhortuneSubscriptionTransactionQuery' => 'applications/phortune/query/PhortuneSubscriptionTransactionQuery.php', + 'PhortuneSubscriptionTransactionType' => 'applications/phortune/xaction/subscription/PhortuneSubscriptionTransactionType.php', 'PhortuneSubscriptionWorker' => 'applications/phortune/worker/PhortuneSubscriptionWorker.php', 'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php', 'PhragmentBrowseController' => 'applications/phragment/controller/PhragmentBrowseController.php', @@ -11812,11 +11819,13 @@ phutil_register_library_map(array( 'PhortuneAccountOrdersController' => 'PhortuneAccountProfileController', 'PhortuneAccountOverviewController' => 'PhortuneAccountProfileController', 'PhortuneAccountPHIDType' => 'PhabricatorPHIDType', - 'PhortuneAccountPaymentMethodListController' => 'PhortuneAccountProfileController', + 'PhortuneAccountPaymentMethodController' => 'PhortuneAccountProfileController', 'PhortuneAccountPaymentMethodViewController' => 'PhortuneAccountController', 'PhortuneAccountProfileController' => 'PhortuneAccountController', 'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhortuneAccountSubscriptionAutopayController' => 'PhortuneAccountController', 'PhortuneAccountSubscriptionController' => 'PhortuneAccountProfileController', + 'PhortuneAccountSubscriptionViewController' => 'PhortuneAccountController', 'PhortuneAccountTransaction' => 'PhabricatorModularTransaction', 'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortuneAccountTransactionType' => 'PhabricatorModularTransactionType', @@ -11953,17 +11962,25 @@ phutil_register_library_map(array( 'PhortuneSubscription' => array( 'PhortuneDAO', 'PhabricatorPolicyInterface', + 'PhabricatorExtendedPolicyInterface', + 'PhabricatorPolicyCodexInterface', + 'PhabricatorApplicationTransactionInterface', ), + 'PhortuneSubscriptionAutopayTransaction' => 'PhortuneSubscriptionTransactionType', 'PhortuneSubscriptionCart' => 'PhortuneCartImplementation', 'PhortuneSubscriptionEditController' => 'PhortuneController', + 'PhortuneSubscriptionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneSubscriptionImplementation' => 'Phobject', 'PhortuneSubscriptionListController' => 'PhortuneController', 'PhortuneSubscriptionPHIDType' => 'PhabricatorPHIDType', + 'PhortuneSubscriptionPolicyCodex' => 'PhabricatorPolicyCodex', 'PhortuneSubscriptionProduct' => 'PhortuneProductImplementation', 'PhortuneSubscriptionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneSubscriptionSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhortuneSubscriptionTableView' => 'AphrontView', - 'PhortuneSubscriptionViewController' => 'PhortuneController', + 'PhortuneSubscriptionTransaction' => 'PhabricatorModularTransaction', + 'PhortuneSubscriptionTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhortuneSubscriptionTransactionType' => 'PhabricatorModularTransactionType', 'PhortuneSubscriptionWorker' => 'PhabricatorWorker', 'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider', 'PhragmentBrowseController' => 'PhragmentController', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 8ec159d024..298c43b8c4 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -43,9 +43,7 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { '(?:query/(?P[^/]+)/)?' => 'PhortuneSubscriptionListController', 'view/(?P\d+)/' - => 'PhortuneSubscriptionViewController', - 'edit/(?P\d+)/' - => 'PhortuneSubscriptionEditController', + => 'PhortuneAccountSubscriptionViewController', 'order/(?P\d+)/' => 'PhortuneCartListController', ), @@ -73,12 +71,18 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { '(?P\d+)/' => array( 'details/' => 'PhortuneAccountDetailsController', 'methods/' => array( - '' => 'PhortuneAccountPaymentMethodListController', + '' => 'PhortuneAccountPaymentMethodController', '(?P\d+)/' => 'PhortuneAccountPaymentMethodViewController', ), 'orders/' => 'PhortuneAccountOrdersController', 'charges/' => 'PhortuneAccountChargesController', - 'subscriptions/' => 'PhortuneAccountSubscriptionController', + 'subscriptions/' => array( + '' => 'PhortuneAccountSubscriptionController', + '(?P\d+)/' => array( + 'autopay/(?P\d+)/' + => 'PhortuneAccountSubscriptionAutopayController', + ), + ), 'managers/' => array( '' => 'PhortuneAccountManagersController', 'add/' => 'PhortuneAccountAddManagerController', @@ -124,7 +128,7 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { '(?:query/(?P[^/]+)/)?' => 'PhortuneSubscriptionListController', 'view/(?P\d+)/' - => 'PhortuneSubscriptionViewController', + => 'PhortuneAccountSubscriptionViewController', 'order/(?P\d+)/' => 'PhortuneCartListController', ), diff --git a/src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php b/src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php index 99b41508e8..389580147a 100644 --- a/src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php +++ b/src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php @@ -12,6 +12,7 @@ final class PhortunePaymentMethodPolicyCodex ->setCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, )) ->setIsActive(true) ->setDescription( diff --git a/src/applications/phortune/codex/PhortuneSubscriptionPolicyCodex.php b/src/applications/phortune/codex/PhortuneSubscriptionPolicyCodex.php new file mode 100644 index 0000000000..484e215663 --- /dev/null +++ b/src/applications/phortune/codex/PhortuneSubscriptionPolicyCodex.php @@ -0,0 +1,36 @@ +getObject(); + + $rules = array(); + + $rules[] = $this->newRule() + ->setCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->setIsActive(true) + ->setDescription( + pht( + 'Account members may view and edit subscriptions.')); + + $rules[] = $this->newRule() + ->setCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + )) + ->setIsActive(true) + ->setDescription( + pht( + 'Merchants you have a relationship with may view associated '. + 'subscriptions.')); + + return $rules; + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php similarity index 97% rename from src/applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php rename to src/applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php index 5fa7cdf4e6..8b616701f2 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php @@ -1,6 +1,6 @@ getViewer(); + $account = $this->getAccount(); + + $subscription = id(new PhortuneSubscriptionQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('subscriptionID'))) + ->withAccountPHIDs(array($account->getPHID())) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$subscription) { + return new Aphront404Response(); + } + + $method = id(new PhortunePaymentMethodQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('methodID'))) + ->withAccountPHIDs(array($subscription->getAccountPHID())) + ->withMerchantPHIDs(array($subscription->getMerchantPHID())) + ->withStatuses( + array( + PhortunePaymentMethod::STATUS_ACTIVE, + )) + ->executeOne(); + if (!$method) { + return new Aphront404Response(); + } + + $next_uri = $subscription->getURI(); + + $autopay_phid = $subscription->getDefaultPaymentMethodPHID(); + $is_stop = ($autopay_phid === $method->getPHID()); + + if ($request->isFormOrHisecPost()) { + if ($is_stop) { + $new_phid = null; + } else { + $new_phid = $method->getPHID(); + } + + $xactions = array(); + + $xactions[] = $subscription->getApplicationTransactionTemplate() + ->setTransactionType( + PhortuneSubscriptionAutopayTransaction::TRANSACTIONTYPE) + ->setNewValue($new_phid); + + $editor = $subscription->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->setCancelURI($next_uri); + + $editor->applyTransactions($subscription, $xactions); + + return id(new AphrontRedirectResponse())->setURI($next_uri); + } + + $method_phid = $method->getPHID(); + $subscription_phid = $subscription->getPHID(); + + $handles = $viewer->loadHandles( + array( + $method_phid, + $subscription_phid, + )); + + $method_handle = $handles[$method_phid]; + $subscription_handle = $handles[$subscription_phid]; + + $method_display = $method_handle->renderLink(); + $method_display = phutil_tag( + 'strong', + array(), + $method_display); + + $subscription_display = $subscription_handle->renderLink(); + $subscription_display = phutil_tag( + 'strong', + array(), + $subscription_display); + + $body = array(); + if ($is_stop) { + $title = pht('Stop Autopay'); + + $body[] = pht( + 'Remove %s as the automatic payment method for subscription %s?', + $method_display, + $subscription_display); + + $body[] = pht( + 'This payment method will no longer be charged automatically.'); + + $submit = pht('Stop Autopay'); + } else { + $title = pht('Start Autopay'); + + $body[] = pht( + 'Set %s as the automatic payment method for subscription %s?', + $method_display, + $subscription_display); + + $body[] = pht( + 'This payment method will be used to automatically pay future '. + 'charges.'); + + $submit = pht('Start Autopay'); + } + + $dialog = $this->newDialog() + ->setTitle($title) + ->addCancelButton($next_uri) + ->addSubmitButton($submit); + + foreach ($body as $graph) { + $dialog->appendParagraph($graph); + } + + return $dialog; + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php new file mode 100644 index 0000000000..ca8c64816e --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php @@ -0,0 +1,338 @@ +getViewer(); + + $subscription = id(new PhortuneSubscriptionQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->needTriggers(true) + ->executeOne(); + if (!$subscription) { + return new Aphront404Response(); + } + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $subscription, + PhabricatorPolicyCapability::CAN_EDIT); + + $merchant = $subscription->getMerchant(); + $account = $subscription->getAccount(); + + $account_id = $account->getID(); + $subscription_id = $subscription->getID(); + + $title = $subscription->getSubscriptionFullName(); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-retweet'); + + $edit_uri = $subscription->getEditURI(); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($subscription->getSubscriptionCrumbName()) + ->setBorder(true); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $next_invoice = $subscription->getTrigger()->getNextEventPrediction(); + $properties->addProperty( + pht('Next Invoice'), + phabricator_datetime($next_invoice, $viewer)); + + $autopay = $this->newAutopayView($subscription); + + $details = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Subscription Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addPropertyList($properties); + + $due_box = $this->buildDueInvoices($subscription); + $invoice_box = $this->buildPastInvoices($subscription); + + $timeline = $this->buildTransactionTimeline( + $subscription, + new PhortuneSubscriptionTransactionQuery()); + $timeline->setShouldTerminate(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter( + array( + $details, + $autopay, + $due_box, + $invoice_box, + $timeline, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } + + private function buildDueInvoices(PhortuneSubscription $subscription) { + $viewer = $this->getViewer(); + + $invoices = id(new PhortuneCartQuery()) + ->setViewer($viewer) + ->withSubscriptionPHIDs(array($subscription->getPHID())) + ->needPurchases(true) + ->withInvoices(true) + ->execute(); + + $phids = array(); + foreach ($invoices as $invoice) { + $phids[] = $invoice->getPHID(); + $phids[] = $invoice->getMerchantPHID(); + foreach ($invoice->getPurchases() as $purchase) { + $phids[] = $purchase->getPHID(); + } + } + $handles = $this->loadViewerHandles($phids); + + $invoice_table = id(new PhortuneOrderTableView()) + ->setUser($viewer) + ->setCarts($invoices) + ->setIsInvoices(true) + ->setHandles($handles); + + $invoice_header = id(new PHUIHeaderView()) + ->setHeader(pht('Invoices Due')); + + return id(new PHUIObjectBoxView()) + ->setHeader($invoice_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($invoice_table); + } + + private function buildPastInvoices(PhortuneSubscription $subscription) { + $viewer = $this->getViewer(); + + $invoices = id(new PhortuneCartQuery()) + ->setViewer($viewer) + ->withSubscriptionPHIDs(array($subscription->getPHID())) + ->needPurchases(true) + ->withStatuses( + array( + PhortuneCart::STATUS_PURCHASING, + PhortuneCart::STATUS_CHARGED, + PhortuneCart::STATUS_HOLD, + PhortuneCart::STATUS_REVIEW, + PhortuneCart::STATUS_PURCHASED, + )) + ->setLimit(50) + ->execute(); + + $phids = array(); + foreach ($invoices as $invoice) { + $phids[] = $invoice->getPHID(); + foreach ($invoice->getPurchases() as $purchase) { + $phids[] = $purchase->getPHID(); + } + } + $handles = $this->loadViewerHandles($phids); + + $invoice_table = id(new PhortuneOrderTableView()) + ->setUser($viewer) + ->setCarts($invoices) + ->setHandles($handles); + + $account = $subscription->getAccount(); + $merchant = $subscription->getMerchant(); + + $account_id = $account->getID(); + $merchant_id = $merchant->getID(); + $subscription_id = $subscription->getID(); + + $invoices_uri = $this->getApplicationURI( + "{$account_id}/subscription/order/{$subscription_id}/"); + + $invoice_header = id(new PHUIHeaderView()) + ->setHeader(pht('Past Invoices')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-list') + ->setHref($invoices_uri) + ->setText(pht('View All Invoices'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($invoice_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($invoice_table); + } + + private function newAutopayView(PhortuneSubscription $subscription) { + $viewer = $this->getViewer(); + $account = $subscription->getAccount(); + + $add_method_uri = urisprintf( + '/phortune/account/%d/card/new/?subscriptionID=%s', + $account->getID(), + $subscription->getID()); + $add_method_uri = $this->getApplicationURI($add_method_uri); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $subscription, + PhabricatorPolicyCapability::CAN_EDIT); + + $methods = id(new PhortunePaymentMethodQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($subscription->getAccountPHID())) + ->withMerchantPHIDs(array($subscription->getMerchantPHID())) + ->withStatuses( + array( + PhortunePaymentMethod::STATUS_ACTIVE, + )) + ->execute(); + $methods = mpull($methods, null, 'getPHID'); + + $autopay_phid = $subscription->getDefaultPaymentMethodPHID(); + $autopay_method = idx($methods, $autopay_phid); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Autopay')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-plus') + ->setHref($add_method_uri) + ->setText(pht('Add Payment Method')) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit)); + + $methods = array_select_keys($methods, array($autopay_phid)) + $methods; + + $rows = array(); + $rowc = array(); + foreach ($methods as $method) { + $is_autopay = ($autopay_method === $method); + + $remove_uri = urisprintf( + '/card/%d/disable/?subscriptionID=%d', + $method->getID(), + $subscription->getID()); + $remove_uri = $this->getApplicationURI($remove_uri); + + $autopay_uri = urisprintf( + '/account/%d/subscriptions/%d/autopay/%d/', + $account->getID(), + $subscription->getID(), + $method->getID()); + $autopay_uri = $this->getApplicationURI($autopay_uri); + + $remove_button = id(new PHUIButtonView()) + ->setTag('a') + ->setColor('grey') + ->setIcon('fa-times') + ->setText(pht('Delete')) + ->setHref($remove_uri) + ->setWorkflow(true) + ->setDisabled(!$can_edit); + + if ($is_autopay) { + $autopay_button = id(new PHUIButtonView()) + ->setColor('red') + ->setIcon('fa-times') + ->setText(pht('Stop Autopay')); + } else { + if ($autopay_method) { + $make_color = 'grey'; + } else { + $make_color = 'green'; + } + + $autopay_button = id(new PHUIButtonView()) + ->setColor($make_color) + ->setIcon('fa-retweet') + ->setText(pht('Start Autopay')); + } + + $autopay_button + ->setTag('a') + ->setHref($autopay_uri) + ->setWorkflow(true) + ->setDisabled(!$can_edit); + + $rows[] = array( + $method->getID(), + phutil_tag( + 'a', + array( + 'href' => $method->getURI(), + ), + $method->getFullDisplayName()), + $method->getDisplayExpires(), + $autopay_button, + $remove_button, + ); + + if ($is_autopay) { + $rowc[] = 'highlighted'; + } else { + $rowc[] = null; + } + } + + $method_table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('ID'), + pht('Payment Method'), + pht('Expires'), + null, + null, + )) + ->setRowClasses($rowc) + ->setColumnClasses( + array( + null, + 'pri wide', + null, + 'right', + null, + )); + + if (!$autopay_method) { + $method_table->setNotice( + array( + id(new PHUIIconView())->setIcon('fa-warning yellow'), + ' ', + pht('Autopay is not currently configured for this subscription.'), + )); + } else { + $method_table->setNotice( + array( + id(new PHUIIconView())->setIcon('fa-check green'), + ' ', + pht( + 'Autopay is configured using %s.', + phutil_tag( + 'a', + array( + 'href' => $autopay_method->getURI(), + ), + $autopay_method->getFullDisplayName())), + )); + } + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($method_table); + } + +} diff --git a/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php b/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php index 46d75ffc04..146ee64a48 100644 --- a/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php +++ b/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php @@ -24,6 +24,21 @@ final class PhortunePaymentMethodDisableController return new Aphront400Response(); } + $subscription_id = $request->getInt('subscriptionID'); + if ($subscription_id) { + $subscription = id(new PhortuneSubscriptionQuery()) + ->setViewer($viewer) + ->withIDs(array($subscription_id)) + ->withAccountPHIDs(array($method->getAccountPHID())) + ->withMerchantPHIDs(array($method->getMerchantPHID())) + ->executeOne(); + if (!$subscription) { + return new Aphront404Response(); + } + } else { + $subscription = null; + } + $account = $method->getAccount(); $account_id = $account->getID(); $account_uri = $account->getPaymentMethodsURI(); @@ -44,18 +59,32 @@ final class PhortunePaymentMethodDisableController $editor->applyTransactions($method, $xactions); - return id(new AphrontRedirectResponse())->setURI($account_uri); + if ($subscription) { + $next_uri = $subscription->getURI(); + } else { + $next_uri = $account_uri; + } + + return id(new AphrontRedirectResponse())->setURI($next_uri); } + $method_phid = $method->getPHID(); + $handles = $viewer->loadHandles( + array( + $method_phid, + )); + + $method_handle = $handles[$method_phid]; + $method_display = $method_handle->renderLink(); + $method_display = phutil_tag('strong', array(), $method_display); + return $this->newDialog() ->setTitle(pht('Remove Payment Method')) + ->addHiddenInput('subscriptionID', $subscription_id) ->appendParagraph( pht( - 'Remove the payment method "%s" from your account?', - phutil_tag( - 'strong', - array(), - $method->getFullDisplayName()))) + 'Remove the payment method %s from your account?', + $method_display)) ->appendParagraph( pht( 'You will no longer be able to make payments using this payment '. diff --git a/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php b/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php deleted file mode 100644 index 2e78d37d5c..0000000000 --- a/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php +++ /dev/null @@ -1,224 +0,0 @@ -getViewer(); - - $authority = $this->loadMerchantAuthority(); - - $subscription_query = id(new PhortuneSubscriptionQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getURIData('id'))) - ->needTriggers(true); - - if ($authority) { - $subscription_query->withMerchantPHIDs(array($authority->getPHID())); - } - - $subscription = $subscription_query->executeOne(); - if (!$subscription) { - return new Aphront404Response(); - } - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $subscription, - PhabricatorPolicyCapability::CAN_EDIT); - - $merchant = $subscription->getMerchant(); - $account = $subscription->getAccount(); - - $account_id = $account->getID(); - $subscription_id = $subscription->getID(); - - $title = $subscription->getSubscriptionFullName(); - - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-calendar-o'); - - $curtain = $this->newCurtainView($subscription); - $edit_uri = $subscription->getEditURI(); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-credit-card') - ->setName(pht('Manage Autopay')) - ->setHref($edit_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - - $crumbs = $this->buildApplicationCrumbs(); - if ($authority) { - $this->addMerchantCrumb($crumbs, $merchant); - } else { - $this->addAccountCrumb($crumbs, $account); - } - $crumbs->addTextCrumb($subscription->getSubscriptionCrumbName()); - $crumbs->setBorder(true); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer); - - $next_invoice = $subscription->getTrigger()->getNextEventPrediction(); - $properties->addProperty( - pht('Next Invoice'), - phabricator_datetime($next_invoice, $viewer)); - - $default_method = $subscription->getDefaultPaymentMethodPHID(); - if ($default_method) { - $method = id(new PhortunePaymentMethodQuery()) - ->setViewer($viewer) - ->withPHIDs(array($default_method)) - ->withStatuses( - array( - PhortunePaymentMethod::STATUS_ACTIVE, - )) - ->executeOne(); - if ($method) { - $handles = $this->loadViewerHandles(array($default_method)); - $autopay_method = $handles[$default_method]->renderLink(); - } else { - $autopay_method = phutil_tag( - 'em', - array(), - pht('')); - } - } else { - $autopay_method = phutil_tag( - 'em', - array(), - pht('No Autopay Method Configured')); - } - - $properties->addProperty( - pht('Autopay With'), - $autopay_method); - - $details = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addPropertyList($properties); - - $due_box = $this->buildDueInvoices($subscription, $authority); - $invoice_box = $this->buildPastInvoices($subscription, $authority); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setCurtain($curtain) - ->setMainColumn(array( - $details, - $due_box, - $invoice_box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - private function buildDueInvoices( - PhortuneSubscription $subscription, - $authority) { - $viewer = $this->getViewer(); - - $invoices = id(new PhortuneCartQuery()) - ->setViewer($viewer) - ->withSubscriptionPHIDs(array($subscription->getPHID())) - ->needPurchases(true) - ->withInvoices(true) - ->execute(); - - $phids = array(); - foreach ($invoices as $invoice) { - $phids[] = $invoice->getPHID(); - $phids[] = $invoice->getMerchantPHID(); - foreach ($invoice->getPurchases() as $purchase) { - $phids[] = $purchase->getPHID(); - } - } - $handles = $this->loadViewerHandles($phids); - - $invoice_table = id(new PhortuneOrderTableView()) - ->setUser($viewer) - ->setCarts($invoices) - ->setIsInvoices(true) - ->setIsMerchantView((bool)$authority) - ->setHandles($handles); - - $invoice_header = id(new PHUIHeaderView()) - ->setHeader(pht('Invoices Due')); - - return id(new PHUIObjectBoxView()) - ->setHeader($invoice_header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($invoice_table); - } - - private function buildPastInvoices( - PhortuneSubscription $subscription, - $authority) { - $viewer = $this->getViewer(); - - $invoices = id(new PhortuneCartQuery()) - ->setViewer($viewer) - ->withSubscriptionPHIDs(array($subscription->getPHID())) - ->needPurchases(true) - ->withStatuses( - array( - PhortuneCart::STATUS_PURCHASING, - PhortuneCart::STATUS_CHARGED, - PhortuneCart::STATUS_HOLD, - PhortuneCart::STATUS_REVIEW, - PhortuneCart::STATUS_PURCHASED, - )) - ->setLimit(50) - ->execute(); - - $phids = array(); - foreach ($invoices as $invoice) { - $phids[] = $invoice->getPHID(); - foreach ($invoice->getPurchases() as $purchase) { - $phids[] = $purchase->getPHID(); - } - } - $handles = $this->loadViewerHandles($phids); - - $invoice_table = id(new PhortuneOrderTableView()) - ->setUser($viewer) - ->setCarts($invoices) - ->setHandles($handles); - - $account = $subscription->getAccount(); - $merchant = $subscription->getMerchant(); - - $account_id = $account->getID(); - $merchant_id = $merchant->getID(); - $subscription_id = $subscription->getID(); - - if ($authority) { - $invoices_uri = $this->getApplicationURI( - "merchant/{$merchant_id}/subscription/order/{$subscription_id}/"); - } else { - $invoices_uri = $this->getApplicationURI( - "{$account_id}/subscription/order/{$subscription_id}/"); - } - - $invoice_header = id(new PHUIHeaderView()) - ->setHeader(pht('Past Invoices')) - ->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setIcon('fa-list') - ->setHref($invoices_uri) - ->setText(pht('View All Invoices'))); - - return id(new PHUIObjectBoxView()) - ->setHeader($invoice_header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($invoice_table); - } - -} diff --git a/src/applications/phortune/editor/PhortuneSubscriptionEditor.php b/src/applications/phortune/editor/PhortuneSubscriptionEditor.php new file mode 100644 index 0000000000..a2314f5650 --- /dev/null +++ b/src/applications/phortune/editor/PhortuneSubscriptionEditor.php @@ -0,0 +1,18 @@ + $handle) { $method = $objects[$phid]; - $id = $method->getID(); - - $handle->setName($method->getFullDisplayName()); + $handle + ->setName($method->getFullDisplayName()) + ->setURI($method->getURI()); } } diff --git a/src/applications/phortune/phid/PhortuneSubscriptionPHIDType.php b/src/applications/phortune/phid/PhortuneSubscriptionPHIDType.php index 6d7275c62b..e07dce12f4 100644 --- a/src/applications/phortune/phid/PhortuneSubscriptionPHIDType.php +++ b/src/applications/phortune/phid/PhortuneSubscriptionPHIDType.php @@ -32,11 +32,9 @@ final class PhortuneSubscriptionPHIDType extends PhabricatorPHIDType { foreach ($handles as $phid => $handle) { $subscription = $objects[$phid]; - $id = $subscription->getID(); - - $handle->setName($subscription->getSubscriptionName()); - $handle->setURI($subscription->getURI()); - + $handle + ->setName($subscription->getSubscriptionName()) + ->setURI($subscription->getURI()); } } diff --git a/src/applications/phortune/query/PhortuneSubscriptionTransactionQuery.php b/src/applications/phortune/query/PhortuneSubscriptionTransactionQuery.php new file mode 100644 index 0000000000..db97925b39 --- /dev/null +++ b/src/applications/phortune/query/PhortuneSubscriptionTransactionQuery.php @@ -0,0 +1,10 @@ +getAccount() - ->getPolicy(PhabricatorPolicyCapability::CAN_EDIT); + return PhabricatorPolicies::getMostOpenPolicy(); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - if ($this->getAccount()->hasAutomaticCapability($capability, $viewer)) { - return true; - } - - // If the viewer controls the merchant this subscription bills to, they can - // view the subscription. - if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { - $can_admin = PhabricatorPolicyFilter::hasCapability( - $viewer, - $this->getMerchant(), - PhabricatorPolicyCapability::CAN_EDIT); - if ($can_admin) { + // See T13366. If you can edit the merchant associated with this + // subscription, you can view the subscription. + if ($capability === PhabricatorPolicyCapability::CAN_VIEW) { + $any_edit = PhortuneMerchantQuery::canViewersEditMerchants( + array($viewer->getPHID()), + array($this->getMerchantPHID())); + if ($any_edit) { return true; } } @@ -284,12 +289,31 @@ final class PhortuneSubscription extends PhortuneDAO return false; } - public function describeAutomaticCapability($capability) { + +/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ + + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + if ($this->hasAutomaticCapability($capability, $viewer)) { + return array(); + } + + // See T13366. For blanket view and edit permissions on all subscriptions, + // you must be able to edit the associated account. return array( - pht('Subscriptions inherit the policies of the associated account.'), - pht( - 'The merchant you are subscribed with can review and manage the '. - 'subscription.'), + array( + $this->getAccount(), + PhabricatorPolicyCapability::CAN_EDIT, + ), ); } + + +/* -( PhabricatorPolicyCodexInterface )------------------------------------ */ + + + public function newPolicyCodex() { + return new PhortuneSubscriptionPolicyCodex(); + } + } diff --git a/src/applications/phortune/storage/PhortuneSubscriptionTransaction.php b/src/applications/phortune/storage/PhortuneSubscriptionTransaction.php new file mode 100644 index 0000000000..78054a5aba --- /dev/null +++ b/src/applications/phortune/storage/PhortuneSubscriptionTransaction.php @@ -0,0 +1,18 @@ +getDefaultPaymentMethodPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setDefaultPaymentMethodPHID($value); + } + + public function getTitle() { + $old_phid = $this->getOldValue(); + $new_phid = $this->getNewValue(); + + if ($old_phid && $new_phid) { + return pht( + '%s changed the automatic payment method for this subscription.', + $this->renderAuthor()); + } else if ($new_phid) { + return pht( + '%s configured an automatic payment method for this subscription.', + $this->renderAuthor()); + } else { + return pht( + '%s stopped automatic payments for this subscription.', + $this->renderAuthor()); + } + } + + public function shouldTryMFA( + $object, + PhabricatorApplicationTransaction $xaction) { + return true; + } + +} diff --git a/src/applications/phortune/xaction/subscription/PhortuneSubscriptionTransactionType.php b/src/applications/phortune/xaction/subscription/PhortuneSubscriptionTransactionType.php new file mode 100644 index 0000000000..676b36122b --- /dev/null +++ b/src/applications/phortune/xaction/subscription/PhortuneSubscriptionTransactionType.php @@ -0,0 +1,4 @@ + Date: Thu, 22 Aug 2019 14:23:23 -0700 Subject: [PATCH 20/27] Update Charge and Cart policies in Phortune, and make URIs more consistent Summary: Ref T13366. Depends on D20721. Continue applying UI and policy updates to the last two Phortune objects. Charges aren't mutable and Carts are already transactional, so this is less involved than prior changes. Test Plan: Viewed various charge/order interfaces as merchants and account members. Maniphest Tasks: T13366 Differential Revision: https://secure.phabricator.com/D20732 --- src/__phutil_library_map__.php | 8 +- .../PhabricatorPhortuneApplication.php | 36 ++++----- .../PhortuneAccountChargeListController.php | 35 +++++++++ .../PhortuneAccountChargesController.php | 2 +- .../account/PhortuneAccountController.php | 2 +- .../PhortuneAccountOrderListController.php | 58 +++++++++++++++ ...PhortuneAccountPaymentMethodController.php | 2 +- .../PhortuneAccountProfileController.php | 18 ++--- ...rtuneAccountSubscriptionViewController.php | 4 +- .../charge/PhortuneChargeListController.php | 74 ------------------- .../query/PhortuneCartSearchEngine.php | 20 +---- .../query/PhortuneChargeSearchEngine.php | 10 --- .../phortune/storage/PhortuneAccount.php | 36 ++++++++- .../phortune/storage/PhortuneCart.php | 42 +++++------ .../phortune/storage/PhortuneCharge.php | 35 +++++++-- .../phortune/storage/PhortuneSubscription.php | 8 +- 16 files changed, 218 insertions(+), 172 deletions(-) create mode 100644 src/applications/phortune/controller/account/PhortuneAccountChargeListController.php create mode 100644 src/applications/phortune/controller/account/PhortuneAccountOrderListController.php delete mode 100644 src/applications/phortune/controller/charge/PhortuneChargeListController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e1afebccf4..4f00d449fa 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5222,6 +5222,7 @@ phutil_register_library_map(array( 'PhortuneAccountAddManagerController' => 'applications/phortune/controller/account/PhortuneAccountAddManagerController.php', 'PhortuneAccountBillingAddressTransaction' => 'applications/phortune/xaction/PhortuneAccountBillingAddressTransaction.php', 'PhortuneAccountBillingNameTransaction' => 'applications/phortune/xaction/PhortuneAccountBillingNameTransaction.php', + 'PhortuneAccountChargeListController' => 'applications/phortune/controller/account/PhortuneAccountChargeListController.php', 'PhortuneAccountChargesController' => 'applications/phortune/controller/account/PhortuneAccountChargesController.php', 'PhortuneAccountController' => 'applications/phortune/controller/account/PhortuneAccountController.php', 'PhortuneAccountDetailsController' => 'applications/phortune/controller/account/PhortuneAccountDetailsController.php', @@ -5246,6 +5247,7 @@ phutil_register_library_map(array( 'PhortuneAccountListController' => 'applications/phortune/controller/account/PhortuneAccountListController.php', 'PhortuneAccountManagersController' => 'applications/phortune/controller/account/PhortuneAccountManagersController.php', 'PhortuneAccountNameTransaction' => 'applications/phortune/xaction/PhortuneAccountNameTransaction.php', + 'PhortuneAccountOrderListController' => 'applications/phortune/controller/account/PhortuneAccountOrderListController.php', 'PhortuneAccountOrdersController' => 'applications/phortune/controller/account/PhortuneAccountOrdersController.php', 'PhortuneAccountOverviewController' => 'applications/phortune/controller/account/PhortuneAccountOverviewController.php', 'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php', @@ -5279,7 +5281,6 @@ phutil_register_library_map(array( 'PhortuneCartUpdateController' => 'applications/phortune/controller/cart/PhortuneCartUpdateController.php', 'PhortuneCartViewController' => 'applications/phortune/controller/cart/PhortuneCartViewController.php', 'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php', - 'PhortuneChargeListController' => 'applications/phortune/controller/charge/PhortuneChargeListController.php', 'PhortuneChargePHIDType' => 'applications/phortune/phid/PhortuneChargePHIDType.php', 'PhortuneChargeQuery' => 'applications/phortune/query/PhortuneChargeQuery.php', 'PhortuneChargeSearchEngine' => 'applications/phortune/query/PhortuneChargeSearchEngine.php', @@ -11787,6 +11788,7 @@ phutil_register_library_map(array( 'PhortuneAccountAddManagerController' => 'PhortuneAccountController', 'PhortuneAccountBillingAddressTransaction' => 'PhortuneAccountTransactionType', 'PhortuneAccountBillingNameTransaction' => 'PhortuneAccountTransactionType', + 'PhortuneAccountChargeListController' => 'PhortuneAccountProfileController', 'PhortuneAccountChargesController' => 'PhortuneAccountProfileController', 'PhortuneAccountController' => 'PhortuneController', 'PhortuneAccountDetailsController' => 'PhortuneAccountProfileController', @@ -11816,6 +11818,7 @@ phutil_register_library_map(array( 'PhortuneAccountListController' => 'PhortuneController', 'PhortuneAccountManagersController' => 'PhortuneAccountProfileController', 'PhortuneAccountNameTransaction' => 'PhortuneAccountTransactionType', + 'PhortuneAccountOrderListController' => 'PhortuneAccountProfileController', 'PhortuneAccountOrdersController' => 'PhortuneAccountProfileController', 'PhortuneAccountOverviewController' => 'PhortuneAccountProfileController', 'PhortuneAccountPHIDType' => 'PhabricatorPHIDType', @@ -11836,6 +11839,7 @@ phutil_register_library_map(array( 'PhortuneDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', + 'PhabricatorExtendedPolicyInterface', ), 'PhortuneCartAcceptController' => 'PhortuneCartController', 'PhortuneCartCancelController' => 'PhortuneCartController', @@ -11855,8 +11859,8 @@ phutil_register_library_map(array( 'PhortuneCharge' => array( 'PhortuneDAO', 'PhabricatorPolicyInterface', + 'PhabricatorExtendedPolicyInterface', ), - 'PhortuneChargeListController' => 'PhortuneController', 'PhortuneChargePHIDType' => 'PhabricatorPHIDType', 'PhortuneChargeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneChargeSearchEngine' => 'PhabricatorApplicationSearchEngine', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 298c43b8c4..20dbc5c88f 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -34,24 +34,6 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { return array( '/phortune/' => array( '' => 'PhortuneLandingController', - '(?P\d+)/' => array( - '' => 'PhortuneAccountOverviewController', - 'card/' => array( - 'new/' => 'PhortunePaymentMethodCreateController', - ), - 'subscription/' => array( - '(?:query/(?P[^/]+)/)?' - => 'PhortuneSubscriptionListController', - 'view/(?P\d+)/' - => 'PhortuneAccountSubscriptionViewController', - 'order/(?P\d+)/' - => 'PhortuneCartListController', - ), - 'order/(?:query/(?P[^/]+)/)?' - => 'PhortuneCartListController', - 'charge/(?:query/(?P[^/]+)/)?' - => 'PhortuneChargeListController', - ), 'card/(?P\d+)/' => array( 'edit/' => 'PhortunePaymentMethodEditController', 'disable/' => 'PhortunePaymentMethodDisableController', @@ -65,22 +47,36 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { ), 'account/' => array( '' => 'PhortuneAccountListController', + $this->getEditRoutePattern('edit/') => 'PhortuneAccountEditController', '(?P\d+)/' => array( + '' => 'PhortuneAccountOverviewController', 'details/' => 'PhortuneAccountDetailsController', 'methods/' => array( '' => 'PhortuneAccountPaymentMethodController', '(?P\d+)/' => 'PhortuneAccountPaymentMethodViewController', + 'new/' => 'PhortunePaymentMethodCreateController', + ), + 'orders/' => array( + '' => 'PhortuneAccountOrdersController', + $this->getQueryRoutePattern('list/') + => 'PhortuneAccountOrderListController', + ), + 'charges/' => array( + '' => 'PhortuneAccountChargesController', + $this->getQueryRoutePattern('list/') + => 'PhortuneAccountChargeListController', ), - 'orders/' => 'PhortuneAccountOrdersController', - 'charges/' => 'PhortuneAccountChargesController', 'subscriptions/' => array( '' => 'PhortuneAccountSubscriptionController', '(?P\d+)/' => array( + '' => 'PhortuneAccountSubscriptionViewController', 'autopay/(?P\d+)/' => 'PhortuneAccountSubscriptionAutopayController', + $this->getQueryRoutePattern('orders/') + => 'PhortuneAccountOrderListController', ), ), 'managers/' => array( diff --git a/src/applications/phortune/controller/account/PhortuneAccountChargeListController.php b/src/applications/phortune/controller/account/PhortuneAccountChargeListController.php new file mode 100644 index 0000000000..882e37f3de --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountChargeListController.php @@ -0,0 +1,35 @@ +getViewer(); + $account = $this->getAccount(); + + return id(new PhortuneChargeSearchEngine()) + ->setAccount($account) + ->setController($this) + ->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + if ($this->hasAccount()) { + $account = $this->getAccount(); + $id = $account->getID(); + + $crumbs->addTextCrumb( + pht('Charges'), + $account->getChargesURI()); + } + + return $crumbs; + } + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountChargesController.php b/src/applications/phortune/controller/account/PhortuneAccountChargesController.php index a899059669..db85698be7 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountChargesController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountChargesController.php @@ -56,7 +56,7 @@ final class PhortuneAccountChargesController $handles = $this->loadViewerHandles($phids); - $charges_uri = $this->getApplicationURI($account->getID().'/charge/'); + $charges_uri = $account->getChargeListURI(); $table = id(new PhortuneChargeTableView()) ->setUser($viewer) diff --git a/src/applications/phortune/controller/account/PhortuneAccountController.php b/src/applications/phortune/controller/account/PhortuneAccountController.php index 8b1a44a81a..d3d60d2789 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountController.php @@ -23,7 +23,7 @@ abstract class PhortuneAccountController abstract protected function shouldRequireAccountEditCapability(); abstract protected function handleAccountRequest(AphrontRequest $request); - private function hasAccount() { + final protected function hasAccount() { return (bool)$this->account; } diff --git a/src/applications/phortune/controller/account/PhortuneAccountOrderListController.php b/src/applications/phortune/controller/account/PhortuneAccountOrderListController.php new file mode 100644 index 0000000000..fd4a3c389f --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountOrderListController.php @@ -0,0 +1,58 @@ +getViewer(); + $account = $this->getAccount(); + + $engine = id(new PhortuneCartSearchEngine()) + ->setController($this) + ->setAccount($account); + + $subscription_id = $request->getURIData('subscriptionID'); + if ($subscription_id) { + $subscription = id(new PhortuneSubscriptionQuery()) + ->setViewer($viewer) + ->withIDs(array($subscription_id)) + ->executeOne(); + if (!$subscription) { + return new Aphront404Response(); + } + + $engine->setSubscription($subscription); + $this->subscription = $subscription; + } + + return $engine->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $subscription = $this->subscription; + if ($subscription) { + $crumbs->addTextCrumb( + $subscription->getObjectName(), + $subscription->getURI()); + } else if ($this->hasAccount()) { + $account = $this->getAccount(); + $id = $account->getID(); + + $crumbs->addTextCrumb( + pht('Orders'), + $account->getOrdersURI()); + } + + return $crumbs; + } + + +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php index 8b616701f2..05a2f33d8d 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php @@ -50,7 +50,7 @@ final class PhortuneAccountPaymentMethodController ->setTag('a') ->setText(pht('Add Payment Method')) ->setIcon('fa-plus') - ->setHref($this->getApplicationURI("{$id}/card/new/")) + ->setHref($this->getApplicationURI("account/{$id}/methods/new/")) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit); diff --git a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php index 522ee08108..739316805f 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php @@ -3,10 +3,6 @@ abstract class PhortuneAccountProfileController extends PhortuneAccountController { - public function buildApplicationMenu() { - return $this->buildSideNavView()->getMenu(); - } - protected function buildHeaderView() { $viewer = $this->getViewer(); $account = $this->getAccount(); @@ -44,7 +40,7 @@ abstract class PhortuneAccountProfileController $nav->addFilter( 'overview', pht('Overview'), - $this->getApplicationURI("/{$id}/"), + $account->getURI(), 'fa-user-circle'); $nav->newLink('details') @@ -59,25 +55,25 @@ abstract class PhortuneAccountProfileController $nav->addFilter( 'methods', pht('Payment Methods'), - $this->getApplicationURI("/account/{$id}/methods/"), + $account->getPaymentMethodsURI(), 'fa-credit-card'); $nav->addFilter( 'subscriptions', pht('Subscriptions'), - $this->getApplicationURI("/account/{$id}/subscriptions/"), + $account->getSubscriptionsURI(), 'fa-retweet'); $nav->addFilter( 'orders', pht('Order History'), - $this->getApplicationURI("/account/{$id}/orders/"), + $account->getOrdersURI(), 'fa-shopping-bag'); $nav->addFilter( 'charges', pht('Charge History'), - $this->getApplicationURI("/account/{$id}/charges/"), + $account->getChargesURI(), 'fa-calculator'); $nav->addLabel(pht('Personnel')); @@ -90,7 +86,7 @@ abstract class PhortuneAccountProfileController $nav->newLink('addresses') ->setname(pht('Email Addresses')) - ->setHref($this->getApplicationURI("/account/{$id}/addresses/")) + ->setHref($account->getEmailAddressesURI()) ->setIcon('fa-envelope-o') ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit); @@ -130,7 +126,7 @@ abstract class PhortuneAccountProfileController } $handles = $this->loadViewerHandles($phids); - $orders_uri = $this->getApplicationURI($account->getID().'/order/'); + $orders_uri = $account->getOrderListURI(); $table = id(new PhortuneOrderTableView()) ->setUser($viewer) diff --git a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php index ca8c64816e..c3e39b94d7 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php @@ -12,7 +12,7 @@ final class PhortuneAccountSubscriptionViewController $subscription = id(new PhortuneSubscriptionQuery()) ->setViewer($viewer) - ->withIDs(array($request->getURIData('id'))) + ->withIDs(array($request->getURIData('subscriptionID'))) ->needTriggers(true) ->executeOne(); if (!$subscription) { @@ -179,7 +179,7 @@ final class PhortuneAccountSubscriptionViewController $account = $subscription->getAccount(); $add_method_uri = urisprintf( - '/phortune/account/%d/card/new/?subscriptionID=%s', + '/account/%d/methods/new/?subscriptionID=%s', $account->getID(), $subscription->getID()); $add_method_uri = $this->getApplicationURI($add_method_uri); diff --git a/src/applications/phortune/controller/charge/PhortuneChargeListController.php b/src/applications/phortune/controller/charge/PhortuneChargeListController.php deleted file mode 100644 index b8edb92507..0000000000 --- a/src/applications/phortune/controller/charge/PhortuneChargeListController.php +++ /dev/null @@ -1,74 +0,0 @@ -getViewer(); - $querykey = $request->getURIData('queryKey'); - $account_id = $request->getURIData('accountID'); - - $engine = new PhortuneChargeSearchEngine(); - - if ($account_id) { - $account = id(new PhortuneAccountQuery()) - ->setViewer($viewer) - ->withIDs(array($account_id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$account) { - return new Aphront404Response(); - } - $this->account = $account; - $engine->setAccount($account); - } else { - return new Aphront404Response(); - } - - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($querykey) - ->setSearchEngine($engine) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - - public function buildSideNavView() { - $viewer = $this->getViewer(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new PhortuneChargeSearchEngine()) - ->setViewer($viewer) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; - } - - protected function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - - $account = $this->account; - if ($account) { - $id = $account->getID(); - $crumbs->addTextCrumb( - $account->getName(), - $this->getApplicationURI("{$id}/")); - $crumbs->addTextCrumb( - pht('Charges'), - $this->getApplicationURI("{$id}/charge/")); - } - - return $crumbs; - } - -} diff --git a/src/applications/phortune/query/PhortuneCartSearchEngine.php b/src/applications/phortune/query/PhortuneCartSearchEngine.php index 193764c2cd..da719c17c0 100644 --- a/src/applications/phortune/query/PhortuneCartSearchEngine.php +++ b/src/applications/phortune/query/PhortuneCartSearchEngine.php @@ -62,26 +62,8 @@ final class PhortuneCartSearchEngine $merchant = $this->getMerchant(); $account = $this->getAccount(); if ($merchant) { - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $merchant, - PhabricatorPolicyCapability::CAN_EDIT); - if (!$can_edit) { - throw new Exception( - pht('You can not query orders for a merchant you do not control.')); - } $query->withMerchantPHIDs(array($merchant->getPHID())); } else if ($account) { - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $account, - PhabricatorPolicyCapability::CAN_EDIT); - if (!$can_edit) { - throw new Exception( - pht( - 'You can not query orders for an account you are not '. - 'a member of.')); - } $query->withAccountPHIDs(array($account->getPHID())); } else { $accounts = id(new PhortuneAccountQuery()) @@ -125,7 +107,7 @@ final class PhortuneCartSearchEngine if ($merchant) { return '/phortune/merchant/orders/'.$merchant->getID().'/'.$path; } else if ($account) { - return '/phortune/'.$account->getID().'/order/'.$path; + return $account->getOrderListURI($path); } else { return '/phortune/order/'.$path; } diff --git a/src/applications/phortune/query/PhortuneChargeSearchEngine.php b/src/applications/phortune/query/PhortuneChargeSearchEngine.php index 0d6c2cfd59..45316118af 100644 --- a/src/applications/phortune/query/PhortuneChargeSearchEngine.php +++ b/src/applications/phortune/query/PhortuneChargeSearchEngine.php @@ -40,16 +40,6 @@ final class PhortuneChargeSearchEngine $account = $this->getAccount(); if ($account) { - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $account, - PhabricatorPolicyCapability::CAN_EDIT); - if (!$can_edit) { - throw new Exception( - pht( - 'You can not query charges for an account you are not '. - 'a member of.')); - } $query->withAccountPHIDs(array($account->getPHID())); } else { $accounts = id(new PhortuneAccountQuery()) diff --git a/src/applications/phortune/storage/PhortuneAccount.php b/src/applications/phortune/storage/PhortuneAccount.php index fcb81ceba3..c823b6a811 100644 --- a/src/applications/phortune/storage/PhortuneAccount.php +++ b/src/applications/phortune/storage/PhortuneAccount.php @@ -102,7 +102,9 @@ final class PhortuneAccount extends PhortuneDAO } public function getURI() { - return '/phortune/'.$this->getID().'/'; + return urisprintf( + '/phortune/account/%d/', + $this->getID()); } public function getDetailsURI() { @@ -111,6 +113,25 @@ final class PhortuneAccount extends PhortuneDAO $this->getID()); } + public function getOrdersURI() { + return urisprintf( + '/phortune/account/%d/orders/', + $this->getID()); + } + + public function getOrderListURI($path = '') { + return urisprintf( + '/phortune/account/%d/orders/list/%s', + $this->getID(), + $path); + } + + public function getSubscriptionsURI() { + return urisprintf( + '/phortune/account/%d/subscriptions/', + $this->getID()); + } + public function getEmailAddressesURI() { return urisprintf( '/phortune/account/%d/addresses/', @@ -123,6 +144,19 @@ final class PhortuneAccount extends PhortuneDAO $this->getID()); } + public function getChargesURI() { + return urisprintf( + '/phortune/account/%d/charges/', + $this->getID()); + } + + public function getChargeListURI($path = '') { + return urisprintf( + '/phortune/account/%d/charges/list/%s', + $this->getID(), + $path); + } + public function attachMerchantPHIDs(array $merchant_phids) { $this->merchantPHIDs = $merchant_phids; return $this; diff --git a/src/applications/phortune/storage/PhortuneCart.php b/src/applications/phortune/storage/PhortuneCart.php index 2b121a3b0c..f551cbb5fb 100644 --- a/src/applications/phortune/storage/PhortuneCart.php +++ b/src/applications/phortune/storage/PhortuneCart.php @@ -3,7 +3,8 @@ final class PhortuneCart extends PhortuneDAO implements PhabricatorApplicationTransactionInterface, - PhabricatorPolicyInterface { + PhabricatorPolicyInterface, + PhabricatorExtendedPolicyInterface { const STATUS_BUILDING = 'cart:building'; const STATUS_READY = 'cart:ready'; @@ -652,26 +653,15 @@ final class PhortuneCart extends PhortuneDAO } public function getPolicy($capability) { - // NOTE: Both view and edit use the account's edit policy. We punch a hole - // through this for merchants, below. - return $this - ->getAccount() - ->getPolicy(PhabricatorPolicyCapability::CAN_EDIT); + return PhabricatorPolicies::getMostOpenPolicy(); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - if ($this->getAccount()->hasAutomaticCapability($capability, $viewer)) { - return true; - } - - // If the viewer controls the merchant this order was placed with, they - // can view the order. - if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { - $can_admin = PhabricatorPolicyFilter::hasCapability( - $viewer, - $this->getMerchant(), - PhabricatorPolicyCapability::CAN_EDIT); - if ($can_admin) { + if ($capability === PhabricatorPolicyCapability::CAN_VIEW) { + $any_edit = PhortuneMerchantQuery::canViewersEditMerchants( + array($viewer->getPHID()), + array($this->getMerchantPHID())); + if ($any_edit) { return true; } } @@ -679,10 +669,20 @@ final class PhortuneCart extends PhortuneDAO return false; } - public function describeAutomaticCapability($capability) { + +/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ + + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + if ($this->hasAutomaticCapability($capability, $viewer)) { + return array(); + } + return array( - pht('Orders inherit the policies of the associated account.'), - pht('The merchant you placed an order with can review and manage it.'), + array( + $this->getAccount(), + PhabricatorPolicyCapability::CAN_EDIT, + ), ); } diff --git a/src/applications/phortune/storage/PhortuneCharge.php b/src/applications/phortune/storage/PhortuneCharge.php index da199d0751..41808a31e5 100644 --- a/src/applications/phortune/storage/PhortuneCharge.php +++ b/src/applications/phortune/storage/PhortuneCharge.php @@ -7,7 +7,9 @@ * charge followed by a successful charge. */ final class PhortuneCharge extends PhortuneDAO - implements PhabricatorPolicyInterface { + implements + PhabricatorPolicyInterface, + PhabricatorExtendedPolicyInterface { const STATUS_CHARGING = 'charge:charging'; const STATUS_CHARGED = 'charge:charged'; @@ -162,19 +164,42 @@ final class PhortuneCharge extends PhortuneDAO public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { - return $this->getAccount()->getPolicy($capability); + return PhabricatorPolicies::getMostOpenPolicy(); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - return $this->getAccount()->hasAutomaticCapability($capability, $viewer); + if ($capability === PhabricatorPolicyCapability::CAN_VIEW) { + $any_edit = PhortuneMerchantQuery::canViewersEditMerchants( + array($viewer->getPHID()), + array($this->getMerchantPHID())); + if ($any_edit) { + return true; + } + } + + return false; } - public function describeAutomaticCapability($capability) { - return pht('Charges inherit the policies of the associated account.'); + +/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ + + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + if ($this->hasAutomaticCapability($capability, $viewer)) { + return array(); + } + + return array( + array( + $this->getAccount(), + PhabricatorPolicyCapability::CAN_EDIT, + ), + ); } } diff --git a/src/applications/phortune/storage/PhortuneSubscription.php b/src/applications/phortune/storage/PhortuneSubscription.php index 260b79a257..84fa23a812 100644 --- a/src/applications/phortune/storage/PhortuneSubscription.php +++ b/src/applications/phortune/storage/PhortuneSubscription.php @@ -189,10 +189,10 @@ final class PhortuneSubscription } public function getURI() { - $account_id = $this->getAccount()->getID(); - $id = $this->getID(); - - return "/phortune/{$account_id}/subscription/view/{$id}/"; + return urisprintf( + '/phortune/account/%d/subscriptions/%d/', + $this->getAccount()->getID(), + $this->getID()); } public function getEditURI() { From 9bcd683c084a27853a5ad3caae292a6bc294beae Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 22 Aug 2019 16:58:12 -0700 Subject: [PATCH 21/27] Update Phortune Merchant UI to bring it in line with Account UI Summary: Depends on D20732. Ref T13366. This generally makes the "Merchant" UI look and work like the "Payment Account" UI. This is mostly simpler since the permissions have largely been sorted out already and there's less going on here and less weirdness around view/edit policies. Test Plan: Browsed all Merchant functions as a merchant member and non-member. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13366 Differential Revision: https://secure.phabricator.com/D20733 --- .../autopatches/20190822.merchant.01.view.sql | 2 + src/__phutil_library_map__.php | 44 ++- .../PhabricatorPhortuneApplication.php | 50 +-- .../controller/PhortuneLandingController.php | 2 +- .../PhortuneAccountEmailViewController.php | 1 - .../account/PhortuneAccountListController.php | 2 +- .../PhortuneAccountOrderListController.php | 5 +- .../PhortuneAccountOverviewController.php | 13 +- .../PhortuneAccountProfileController.php | 12 +- ...rtuneAccountSubscriptionViewController.php | 25 +- .../cart/PhortuneCartListController.php | 134 -------- .../PhortuneMerchantAddManagerController.php | 37 +- .../merchant/PhortuneMerchantController.php | 81 ++++- .../PhortuneMerchantDetailsController.php | 151 ++++++++ .../PhortuneMerchantEditController.php | 2 +- ...hortuneMerchantInvoiceCreateController.php | 11 +- .../PhortuneMerchantListController.php | 33 +- ...=> PhortuneMerchantManagersController.php} | 43 ++- .../PhortuneMerchantOrderListController.php | 55 +++ .../PhortuneMerchantOrdersController.php | 78 +++++ .../PhortuneMerchantOverviewController.php | 136 ++++++++ .../PhortuneMerchantPictureController.php | 30 +- .../PhortuneMerchantProfileController.php | 91 ++--- ...tuneMerchantProviderDisableController.php} | 15 +- ...hortuneMerchantProviderEditController.php} | 47 ++- ...PhortuneMerchantProviderViewController.php | 127 +++++++ .../PhortuneMerchantProvidersController.php | 116 +++++++ ...tuneMerchantSubscriptionListController.php | 50 +++ ...hortuneMerchantSubscriptionsController.php | 68 ++++ .../PhortuneMerchantViewController.php | 324 ------------------ .../editor/PhortuneMerchantEditEngine.php | 2 +- .../editor/PhortuneMerchantEditor.php | 1 - .../provider/PhortuneTestPaymentProvider.php | 2 +- .../phortune/storage/PhortuneMerchant.php | 47 ++- .../storage/PhortunePaymentProviderConfig.php | 12 + .../phortune/view/PhortuneOrderTableView.php | 22 +- 36 files changed, 1122 insertions(+), 749 deletions(-) create mode 100644 resources/sql/autopatches/20190822.merchant.01.view.sql delete mode 100644 src/applications/phortune/controller/cart/PhortuneCartListController.php create mode 100644 src/applications/phortune/controller/merchant/PhortuneMerchantDetailsController.php rename src/applications/phortune/controller/merchant/{PhortuneMerchantManagerController.php => PhortuneMerchantManagersController.php} (73%) create mode 100644 src/applications/phortune/controller/merchant/PhortuneMerchantOrderListController.php create mode 100644 src/applications/phortune/controller/merchant/PhortuneMerchantOrdersController.php create mode 100644 src/applications/phortune/controller/merchant/PhortuneMerchantOverviewController.php rename src/applications/phortune/controller/{provider/PhortuneProviderDisableController.php => merchant/PhortuneMerchantProviderDisableController.php} (84%) rename src/applications/phortune/controller/{provider/PhortuneProviderEditController.php => merchant/PhortuneMerchantProviderEditController.php} (87%) create mode 100644 src/applications/phortune/controller/merchant/PhortuneMerchantProviderViewController.php create mode 100644 src/applications/phortune/controller/merchant/PhortuneMerchantProvidersController.php create mode 100644 src/applications/phortune/controller/merchant/PhortuneMerchantSubscriptionListController.php create mode 100644 src/applications/phortune/controller/merchant/PhortuneMerchantSubscriptionsController.php delete mode 100644 src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php diff --git a/resources/sql/autopatches/20190822.merchant.01.view.sql b/resources/sql/autopatches/20190822.merchant.01.view.sql new file mode 100644 index 0000000000..cb609f054e --- /dev/null +++ b/resources/sql/autopatches/20190822.merchant.01.view.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phortune.phortune_merchant + DROP viewPolicy; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4f00d449fa..5fee952d0a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5271,7 +5271,6 @@ phutil_register_library_map(array( 'PhortuneCartController' => 'applications/phortune/controller/cart/PhortuneCartController.php', 'PhortuneCartEditor' => 'applications/phortune/editor/PhortuneCartEditor.php', 'PhortuneCartImplementation' => 'applications/phortune/cart/PhortuneCartImplementation.php', - 'PhortuneCartListController' => 'applications/phortune/controller/cart/PhortuneCartListController.php', 'PhortuneCartPHIDType' => 'applications/phortune/phid/PhortuneCartPHIDType.php', 'PhortuneCartQuery' => 'applications/phortune/query/PhortuneCartQuery.php', 'PhortuneCartReplyHandler' => 'applications/phortune/mail/PhortuneCartReplyHandler.php', @@ -5304,6 +5303,7 @@ phutil_register_library_map(array( 'PhortuneMerchantContactInfoTransaction' => 'applications/phortune/xaction/PhortuneMerchantContactInfoTransaction.php', 'PhortuneMerchantController' => 'applications/phortune/controller/merchant/PhortuneMerchantController.php', 'PhortuneMerchantDescriptionTransaction' => 'applications/phortune/xaction/PhortuneMerchantDescriptionTransaction.php', + 'PhortuneMerchantDetailsController' => 'applications/phortune/controller/merchant/PhortuneMerchantDetailsController.php', 'PhortuneMerchantEditController' => 'applications/phortune/controller/merchant/PhortuneMerchantEditController.php', 'PhortuneMerchantEditEngine' => 'applications/phortune/editor/PhortuneMerchantEditEngine.php', 'PhortuneMerchantEditor' => 'applications/phortune/editor/PhortuneMerchantEditor.php', @@ -5313,18 +5313,26 @@ phutil_register_library_map(array( 'PhortuneMerchantInvoiceEmailTransaction' => 'applications/phortune/xaction/PhortuneMerchantInvoiceEmailTransaction.php', 'PhortuneMerchantInvoiceFooterTransaction' => 'applications/phortune/xaction/PhortuneMerchantInvoiceFooterTransaction.php', 'PhortuneMerchantListController' => 'applications/phortune/controller/merchant/PhortuneMerchantListController.php', - 'PhortuneMerchantManagerController' => 'applications/phortune/controller/merchant/PhortuneMerchantManagerController.php', + 'PhortuneMerchantManagersController' => 'applications/phortune/controller/merchant/PhortuneMerchantManagersController.php', 'PhortuneMerchantNameTransaction' => 'applications/phortune/xaction/PhortuneMerchantNameTransaction.php', + 'PhortuneMerchantOrderListController' => 'applications/phortune/controller/merchant/PhortuneMerchantOrderListController.php', + 'PhortuneMerchantOrdersController' => 'applications/phortune/controller/merchant/PhortuneMerchantOrdersController.php', + 'PhortuneMerchantOverviewController' => 'applications/phortune/controller/merchant/PhortuneMerchantOverviewController.php', 'PhortuneMerchantPHIDType' => 'applications/phortune/phid/PhortuneMerchantPHIDType.php', 'PhortuneMerchantPictureController' => 'applications/phortune/controller/merchant/PhortuneMerchantPictureController.php', 'PhortuneMerchantPictureTransaction' => 'applications/phortune/xaction/PhortuneMerchantPictureTransaction.php', 'PhortuneMerchantProfileController' => 'applications/phortune/controller/merchant/PhortuneMerchantProfileController.php', + 'PhortuneMerchantProviderDisableController' => 'applications/phortune/controller/merchant/PhortuneMerchantProviderDisableController.php', + 'PhortuneMerchantProviderEditController' => 'applications/phortune/controller/merchant/PhortuneMerchantProviderEditController.php', + 'PhortuneMerchantProviderViewController' => 'applications/phortune/controller/merchant/PhortuneMerchantProviderViewController.php', + 'PhortuneMerchantProvidersController' => 'applications/phortune/controller/merchant/PhortuneMerchantProvidersController.php', 'PhortuneMerchantQuery' => 'applications/phortune/query/PhortuneMerchantQuery.php', 'PhortuneMerchantSearchEngine' => 'applications/phortune/query/PhortuneMerchantSearchEngine.php', + 'PhortuneMerchantSubscriptionListController' => 'applications/phortune/controller/merchant/PhortuneMerchantSubscriptionListController.php', + 'PhortuneMerchantSubscriptionsController' => 'applications/phortune/controller/merchant/PhortuneMerchantSubscriptionsController.php', 'PhortuneMerchantTransaction' => 'applications/phortune/storage/PhortuneMerchantTransaction.php', 'PhortuneMerchantTransactionQuery' => 'applications/phortune/query/PhortuneMerchantTransactionQuery.php', 'PhortuneMerchantTransactionType' => 'applications/phortune/xaction/PhortuneMerchantTransactionType.php', - 'PhortuneMerchantViewController' => 'applications/phortune/controller/merchant/PhortuneMerchantViewController.php', 'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php', 'PhortuneOrderTableView' => 'applications/phortune/view/PhortuneOrderTableView.php', 'PhortunePayPalPaymentProvider' => 'applications/phortune/provider/PhortunePayPalPaymentProvider.php', @@ -5356,8 +5364,6 @@ phutil_register_library_map(array( 'PhortuneProductQuery' => 'applications/phortune/query/PhortuneProductQuery.php', 'PhortuneProductViewController' => 'applications/phortune/controller/product/PhortuneProductViewController.php', 'PhortuneProviderActionController' => 'applications/phortune/controller/provider/PhortuneProviderActionController.php', - 'PhortuneProviderDisableController' => 'applications/phortune/controller/provider/PhortuneProviderDisableController.php', - 'PhortuneProviderEditController' => 'applications/phortune/controller/provider/PhortuneProviderEditController.php', 'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php', 'PhortunePurchasePHIDType' => 'applications/phortune/phid/PhortunePurchasePHIDType.php', 'PhortunePurchaseQuery' => 'applications/phortune/query/PhortunePurchaseQuery.php', @@ -11847,7 +11853,6 @@ phutil_register_library_map(array( 'PhortuneCartController' => 'PhortuneController', 'PhortuneCartEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneCartImplementation' => 'Phobject', - 'PhortuneCartListController' => 'PhortuneController', 'PhortuneCartPHIDType' => 'PhabricatorPHIDType', 'PhortuneCartQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneCartReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', @@ -11883,32 +11888,41 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), - 'PhortuneMerchantAddManagerController' => 'PhortuneController', + 'PhortuneMerchantAddManagerController' => 'PhortuneMerchantController', 'PhortuneMerchantCapability' => 'PhabricatorPolicyCapability', 'PhortuneMerchantContactInfoTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantController' => 'PhortuneController', 'PhortuneMerchantDescriptionTransaction' => 'PhortuneMerchantTransactionType', - 'PhortuneMerchantEditController' => 'PhortuneMerchantController', + 'PhortuneMerchantDetailsController' => 'PhortuneMerchantProfileController', + 'PhortuneMerchantEditController' => 'PhortuneController', 'PhortuneMerchantEditEngine' => 'PhabricatorEditEngine', 'PhortuneMerchantEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneMerchantHasAccountEdgeType' => 'PhabricatorEdgeType', 'PhortuneMerchantHasMemberEdgeType' => 'PhabricatorEdgeType', - 'PhortuneMerchantInvoiceCreateController' => 'PhortuneMerchantProfileController', + 'PhortuneMerchantInvoiceCreateController' => 'PhortuneMerchantController', 'PhortuneMerchantInvoiceEmailTransaction' => 'PhortuneMerchantTransactionType', 'PhortuneMerchantInvoiceFooterTransaction' => 'PhortuneMerchantTransactionType', - 'PhortuneMerchantListController' => 'PhortuneMerchantController', - 'PhortuneMerchantManagerController' => 'PhortuneMerchantProfileController', + 'PhortuneMerchantListController' => 'PhortuneController', + 'PhortuneMerchantManagersController' => 'PhortuneMerchantProfileController', 'PhortuneMerchantNameTransaction' => 'PhortuneMerchantTransactionType', + 'PhortuneMerchantOrderListController' => 'PhortuneMerchantProfileController', + 'PhortuneMerchantOrdersController' => 'PhortuneMerchantProfileController', + 'PhortuneMerchantOverviewController' => 'PhortuneMerchantProfileController', 'PhortuneMerchantPHIDType' => 'PhabricatorPHIDType', - 'PhortuneMerchantPictureController' => 'PhortuneMerchantProfileController', + 'PhortuneMerchantPictureController' => 'PhortuneMerchantController', 'PhortuneMerchantPictureTransaction' => 'PhortuneMerchantTransactionType', - 'PhortuneMerchantProfileController' => 'PhortuneController', + 'PhortuneMerchantProfileController' => 'PhortuneMerchantController', + 'PhortuneMerchantProviderDisableController' => 'PhortuneMerchantController', + 'PhortuneMerchantProviderEditController' => 'PhortuneMerchantController', + 'PhortuneMerchantProviderViewController' => 'PhortuneMerchantController', + 'PhortuneMerchantProvidersController' => 'PhortuneMerchantProfileController', 'PhortuneMerchantQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneMerchantSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhortuneMerchantSubscriptionListController' => 'PhortuneMerchantProfileController', + 'PhortuneMerchantSubscriptionsController' => 'PhortuneMerchantProfileController', 'PhortuneMerchantTransaction' => 'PhabricatorModularTransaction', 'PhortuneMerchantTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortuneMerchantTransactionType' => 'PhabricatorModularTransactionType', - 'PhortuneMerchantViewController' => 'PhortuneMerchantProfileController', 'PhortuneMonthYearExpiryControl' => 'AphrontFormControl', 'PhortuneOrderTableView' => 'AphrontView', 'PhortunePayPalPaymentProvider' => 'PhortunePaymentProvider', @@ -11953,8 +11967,6 @@ phutil_register_library_map(array( 'PhortuneProductQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneProductViewController' => 'PhortuneController', 'PhortuneProviderActionController' => 'PhortuneController', - 'PhortuneProviderDisableController' => 'PhortuneMerchantController', - 'PhortuneProviderEditController' => 'PhortuneMerchantController', 'PhortunePurchase' => array( 'PhortuneDAO', 'PhabricatorPolicyInterface', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 20dbc5c88f..15eb500fc3 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -103,36 +103,40 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { => 'PhortuneProviderActionController', ), 'merchant/' => array( - '(?:query/(?P[^/]+)/)?' => 'PhortuneMerchantListController', - 'picture/(?:(?P\d+)/)?' => 'PhortuneMerchantPictureController', + $this->getQueryRoutePattern() + => 'PhortuneMerchantListController', $this->getEditRoutePattern('edit/') => 'PhortuneMerchantEditController', - 'orders/(?P\d+)/(?:query/(?P[^/]+)/)?' - => 'PhortuneCartListController', - 'manager/' => array( - '(?:(?P\d+)/)?' => 'PhortuneMerchantManagerController', - 'add/(?:(?P\d+)/)?' => 'PhortuneMerchantAddManagerController', - ), '(?P\d+)/' => array( - 'cart/(?P\d+)/' => array( - '' => 'PhortuneCartViewController', - '(?Pcancel|refund)/' => 'PhortuneCartCancelController', - 'update/' => 'PhortuneCartUpdateController', - 'accept/' => 'PhortuneCartAcceptController', + '' => 'PhortuneMerchantOverviewController', + 'details/' => 'PhortuneMerchantDetailsController', + 'providers/' => array( + '' => 'PhortuneMerchantProvidersController', + '(?P\d+)/' => array( + '' => 'PhortuneMerchantProviderViewController', + 'disable/' => 'PhortuneMerchantProviderDisableController', + ), + $this->getEditRoutePattern('edit/') + => 'PhortuneMerchantProviderEditController', ), - 'subscription/' => array( - '(?:query/(?P[^/]+)/)?' - => 'PhortuneSubscriptionListController', - 'view/(?P\d+)/' - => 'PhortuneAccountSubscriptionViewController', - 'order/(?P\d+)/' - => 'PhortuneCartListController', + 'orders/' => array( + '' => 'PhortuneMerchantOrdersController', + $this->getQueryRoutePattern('list/') + => 'PhortuneMerchantOrderListController', ), - 'invoice/' => array( - 'new/' => 'PhortuneMerchantInvoiceCreateController', + 'picture/' => array( + 'edit/' => 'PhortuneMerchantPictureController', + ), + 'subscriptions/' => array( + '' => 'PhortuneMerchantSubscriptionsController', + $this->getQueryRoutePattern('list/') + => 'PhortuneMerchantSubscriptionListController', + ), + 'managers/' => array( + '' => 'PhortuneMerchantManagersController', + 'new/' => 'PhortuneMerchantAddManagerController', ), ), - '(?P\d+)/' => 'PhortuneMerchantViewController', ), ), ); diff --git a/src/applications/phortune/controller/PhortuneLandingController.php b/src/applications/phortune/controller/PhortuneLandingController.php index e6906095d2..e20d222a86 100644 --- a/src/applications/phortune/controller/PhortuneLandingController.php +++ b/src/applications/phortune/controller/PhortuneLandingController.php @@ -11,7 +11,7 @@ final class PhortuneLandingController extends PhortuneController { if (count($accounts) == 1) { $account = head($accounts); - $next_uri = $this->getApplicationURI($account->getID().'/'); + $next_uri = $account->getURI(); } else { $next_uri = $this->getApplicationURI('account/'); } diff --git a/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php b/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php index 7732518d72..14c2b842f9 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php @@ -50,7 +50,6 @@ final class PhortuneAccountEmailViewController ->setTitle($address->getObjectName()) ->setCrumbs($crumbs) ->appendChild($view); - } private function buildCurtainView(PhortuneAccountEmail $address) { diff --git a/src/applications/phortune/controller/account/PhortuneAccountListController.php b/src/applications/phortune/controller/account/PhortuneAccountListController.php index 177f84d75d..0ac8cecd8b 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountListController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountListController.php @@ -38,7 +38,7 @@ final class PhortuneAccountListController extends PhortuneController { $item = id(new PHUIObjectItemView()) ->setSubhead(pht('Account %d', $account->getID())) ->setHeader($account->getName()) - ->setHref($this->getApplicationURI($account->getID().'/')) + ->setHref($account->getURI()) ->setObject($account) ->setImageIcon('fa-user-circle'); diff --git a/src/applications/phortune/controller/account/PhortuneAccountOrderListController.php b/src/applications/phortune/controller/account/PhortuneAccountOrderListController.php index fd4a3c389f..afdfc9fcce 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountOrderListController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountOrderListController.php @@ -44,11 +44,8 @@ final class PhortuneAccountOrderListController $subscription->getURI()); } else if ($this->hasAccount()) { $account = $this->getAccount(); - $id = $account->getID(); - $crumbs->addTextCrumb( - pht('Orders'), - $account->getOrdersURI()); + $crumbs->addTextCrumb(pht('Orders'), $account->getOrdersURI()); } return $crumbs; diff --git a/src/applications/phortune/controller/account/PhortuneAccountOverviewController.php b/src/applications/phortune/controller/account/PhortuneAccountOverviewController.php index 078599faca..3b8e8497db 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountOverviewController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountOverviewController.php @@ -66,22 +66,11 @@ final class PhortuneAccountOverviewController $viewer = $this->getViewer(); - $phids = array(); - foreach ($carts as $cart) { - $phids[] = $cart->getPHID(); - $phids[] = $cart->getMerchantPHID(); - foreach ($cart->getPurchases() as $purchase) { - $phids[] = $purchase->getPHID(); - } - } - $handles = $this->loadViewerHandles($phids); - $table = id(new PhortuneOrderTableView()) ->setNoDataString(pht('You have no unpaid invoices.')) ->setIsInvoices(true) ->setUser($viewer) - ->setCarts($carts) - ->setHandles($handles); + ->setCarts($carts); $header = id(new PHUIHeaderView()) ->setHeader(pht('Invoices Due')); diff --git a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php index 739316805f..33361c1f66 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php @@ -117,21 +117,11 @@ abstract class PhortuneAccountProfileController ->setLimit($limit) ->execute(); - $phids = array(); - foreach ($carts as $cart) { - $phids[] = $cart->getPHID(); - foreach ($cart->getPurchases() as $purchase) { - $phids[] = $purchase->getPHID(); - } - } - $handles = $this->loadViewerHandles($phids); - $orders_uri = $account->getOrderListURI(); $table = id(new PhortuneOrderTableView()) ->setUser($viewer) - ->setCarts($carts) - ->setHandles($handles); + ->setCarts($carts); $header = id(new PHUIHeaderView()) ->setHeader(pht('Recent Orders')) diff --git a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php index c3e39b94d7..e38c18779b 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php @@ -92,21 +92,10 @@ final class PhortuneAccountSubscriptionViewController ->withInvoices(true) ->execute(); - $phids = array(); - foreach ($invoices as $invoice) { - $phids[] = $invoice->getPHID(); - $phids[] = $invoice->getMerchantPHID(); - foreach ($invoice->getPurchases() as $purchase) { - $phids[] = $purchase->getPHID(); - } - } - $handles = $this->loadViewerHandles($phids); - $invoice_table = id(new PhortuneOrderTableView()) ->setUser($viewer) ->setCarts($invoices) - ->setIsInvoices(true) - ->setHandles($handles); + ->setIsInvoices(true); $invoice_header = id(new PHUIHeaderView()) ->setHeader(pht('Invoices Due')); @@ -135,19 +124,9 @@ final class PhortuneAccountSubscriptionViewController ->setLimit(50) ->execute(); - $phids = array(); - foreach ($invoices as $invoice) { - $phids[] = $invoice->getPHID(); - foreach ($invoice->getPurchases() as $purchase) { - $phids[] = $purchase->getPHID(); - } - } - $handles = $this->loadViewerHandles($phids); - $invoice_table = id(new PhortuneOrderTableView()) ->setUser($viewer) - ->setCarts($invoices) - ->setHandles($handles); + ->setCarts($invoices); $account = $subscription->getAccount(); $merchant = $subscription->getMerchant(); diff --git a/src/applications/phortune/controller/cart/PhortuneCartListController.php b/src/applications/phortune/controller/cart/PhortuneCartListController.php deleted file mode 100644 index 0537417f7c..0000000000 --- a/src/applications/phortune/controller/cart/PhortuneCartListController.php +++ /dev/null @@ -1,134 +0,0 @@ -getViewer(); - - $merchant_id = $request->getURIData('merchantID'); - $account_id = $request->getURIData('accountID'); - $subscription_id = $request->getURIData('subscriptionID'); - - $engine = id(new PhortuneCartSearchEngine()) - ->setViewer($viewer); - - if ($merchant_id) { - $merchant = id(new PhortuneMerchantQuery()) - ->setViewer($viewer) - ->withIDs(array($merchant_id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$merchant) { - return new Aphront404Response(); - } - $this->merchant = $merchant; - $viewer->grantAuthority($merchant); - $engine->setMerchant($merchant); - } else if ($account_id) { - $account = id(new PhortuneAccountQuery()) - ->setViewer($viewer) - ->withIDs(array($account_id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$account) { - return new Aphront404Response(); - } - $this->account = $account; - $engine->setAccount($account); - } else { - return new Aphront404Response(); - } - - // NOTE: We must process this after processing the merchant authority, so - // it becomes visible in merchant contexts. - if ($subscription_id) { - $subscription = id(new PhortuneSubscriptionQuery()) - ->setViewer($viewer) - ->withIDs(array($subscription_id)) - ->executeOne(); - if (!$subscription) { - return new Aphront404Response(); - } - $this->subscription = $subscription; - $engine->setSubscription($subscription); - } - - $this->engine = $engine; - - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine($engine) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - - public function buildSideNavView() { - $viewer = $this->getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - $this->engine->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; - } - - protected function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - - $subscription = $this->subscription; - - $merchant = $this->merchant; - if ($merchant) { - $id = $merchant->getID(); - $this->addMerchantCrumb($crumbs, $merchant); - if (!$subscription) { - $crumbs->addTextCrumb( - pht('Orders'), - $this->getApplicationURI("merchant/orders/{$id}/")); - } - } - - $account = $this->account; - if ($account) { - $id = $account->getID(); - $this->addAccountCrumb($crumbs, $account); - if (!$subscription) { - $crumbs->addTextCrumb( - pht('Orders'), - $this->getApplicationURI("{$id}/order/")); - } - } - - if ($subscription) { - if ($merchant) { - $subscription_uri = $subscription->getMerchantURI(); - } else { - $subscription_uri = $subscription->getURI(); - } - $crumbs->addTextCrumb( - $subscription->getSubscriptionName(), - $subscription_uri); - } - - return $crumbs; - } - -} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantAddManagerController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantAddManagerController.php index 3ef0a53874..0f4bb479e1 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantAddManagerController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantAddManagerController.php @@ -1,32 +1,23 @@ getViewer(); - $id = $request->getURIData('id'); - - $merchant = id(new PhortuneMerchantQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needProfileImage(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$merchant) { - return new Aphront404Response(); - } + $merchant = $this->getMerchant(); $v_members = array(); $e_members = null; - $merchant_uri = $this->getApplicationURI("/merchant/manager/{$id}/"); + $merchant_uri = $merchant->getManagersURI(); - if ($request->isFormPost()) { + if ($request->isFormOrHiSecPost()) { $xactions = array(); - $v_members = $request->getArr('memberPHIDs'); + $v_members = $request->getArr('managerPHIDs'); $type_edge = PhabricatorTransactions::TYPE_EDGE; $xactions[] = id(new PhortuneMerchantTransaction()) @@ -59,13 +50,13 @@ final class PhortuneMerchantAddManagerController extends PhortuneController { ->appendControl( id(new AphrontFormTokenizerControl()) ->setDatasource(new PhabricatorPeopleDatasource()) - ->setLabel(pht('Members')) - ->setName('memberPHIDs') + ->setLabel(pht('New Managers')) + ->setName('managerPHIDs') ->setValue($v_members) ->setError($e_members)); return $this->newDialog() - ->setTitle(pht('Add New Manager')) + ->setTitle(pht('Add New Managers')) ->appendForm($form) ->setWidth(AphrontDialogView::WIDTH_FORM) ->addCancelButton($merchant_uri) diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantController.php index 23b96197a0..15c4e0047a 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantController.php @@ -3,11 +3,80 @@ abstract class PhortuneMerchantController extends PhortuneController { - protected function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - $crumbs->addTextCrumb( - pht('Merchants'), - $this->getApplicationURI('merchant/')); - return $crumbs; + private $merchant; + + final protected function setMerchant(PhortuneMerchant $merchant) { + $this->merchant = $merchant; + return $this; } + + final protected function getMerchant() { + return $this->merchant; + } + + final protected function hasMerchant() { + return (bool)$this->merchant; + } + + final public function handleRequest(AphrontRequest $request) { + if ($this->shouldRequireMerchantEditCapability()) { + $response = $this->loadMerchantForEdit(); + } else { + $response = $this->loadMerchantForView(); + } + + if ($response) { + return $response; + } + + return $this->handleMerchantRequest($request); + } + + abstract protected function shouldRequireMerchantEditCapability(); + abstract protected function handleMerchantRequest(AphrontRequest $request); + + private function loadMerchantForEdit() { + return $this->loadMerchantWithCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )); + } + + private function loadMerchantForView() { + return $this->loadMerchantWithCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + )); + } + + private function loadMerchantWithCapabilities(array $capabilities) { + $viewer = $this->getViewer(); + $request = $this->getRequest(); + + $merchant_id = $request->getURIData('merchantID'); + if (!$merchant_id) { + throw new Exception( + pht( + 'Controller ("%s") extends controller "%s", but is reachable '. + 'with no "merchantID" in URI.', + get_class($this), + __CLASS__)); + } + + $merchant = id(new PhortuneMerchantQuery()) + ->setViewer($viewer) + ->withIDs(array($merchant_id)) + ->needProfileImage(true) + ->requireCapabilities($capabilities) + ->executeOne(); + if (!$merchant) { + return new Aphront404Response(); + } + + $this->setMerchant($merchant); + + return null; + } + } diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantDetailsController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantDetailsController.php new file mode 100644 index 0000000000..8b558ac387 --- /dev/null +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantDetailsController.php @@ -0,0 +1,151 @@ +getViewer(); + $id = $request->getURIData('id'); + + $merchant = $this->getMerchant(); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Account Details')) + ->setBorder(true); + $header = $this->buildHeaderView(); + + $title = pht( + '%s %s', + $merchant->getObjectName(), + $merchant->getName()); + + $details = $this->buildDetailsView($merchant); + $curtain = $this->buildCurtainView($merchant); + + $timeline = $this->buildTransactionTimeline( + $merchant, + new PhortuneMerchantTransactionQuery()); + $timeline->setShouldTerminate(true); + + $navigation = $this->buildSideNavView('details'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $details, + $timeline, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + } + + private function buildDetailsView(PhortuneMerchant $merchant) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($merchant); + + $invoice_from = $merchant->getInvoiceEmail(); + if (!$invoice_from) { + $invoice_from = pht('No email address set'); + $invoice_from = phutil_tag('em', array(), $invoice_from); + } + $view->addProperty(pht('Invoice From'), $invoice_from); + + $description = $merchant->getDescription(); + if (strlen($description)) { + $description = new PHUIRemarkupView($viewer, $description); + $view->addSectionHeader( + pht('Description'), + PHUIPropertyListView::ICON_SUMMARY); + $view->addTextContent($description); + } + + $contact_info = $merchant->getContactInfo(); + if (strlen($contact_info)) { + $contact_info = new PHUIRemarkupView($viewer, $contact_info); + $view->addSectionHeader( + pht('Contact Information'), + PHUIPropertyListView::ICON_SUMMARY); + $view->addTextContent($contact_info); + } + + $footer_info = $merchant->getInvoiceFooter(); + if (strlen($footer_info)) { + $footer_info = new PHUIRemarkupView($viewer, $footer_info); + $view->addSectionHeader( + pht('Invoice Footer'), + PHUIPropertyListView::ICON_SUMMARY); + $view->addTextContent($footer_info); + } + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); + } + + private function buildCurtainView(PhortuneMerchant $merchant) { + $viewer = $this->getRequest()->getUser(); + $id = $merchant->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $merchant, + PhabricatorPolicyCapability::CAN_EDIT); + + $curtain = $this->newCurtainView($merchant); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Merchant')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($this->getApplicationURI("merchant/edit/{$id}/"))); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Logo')) + ->setIcon('fa-picture-o') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($this->getApplicationURI("merchant/{$id}/picture/edit/"))); + + $member_phids = $merchant->getMemberPHIDs(); + $handles = $viewer->loadHandles($member_phids); + + $member_list = id(new PHUIObjectItemListView()) + ->setSimple(true); + + foreach ($member_phids as $member_phid) { + $image_uri = $handles[$member_phid]->getImageURI(); + $image_href = $handles[$member_phid]->getURI(); + $person = $handles[$member_phid]; + + $member = id(new PHUIObjectItemView()) + ->setImageURI($image_uri) + ->setHref($image_href) + ->setHeader($person->getFullName()); + + $member_list->addItem($member); + } + + $curtain->newPanel() + ->setHeaderText(pht('Managers')) + ->appendChild($member_list); + + return $curtain; + } + +} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantEditController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantEditController.php index 07c743251c..78125d43d6 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantEditController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantEditController.php @@ -1,7 +1,7 @@ getUser(); $merchant = $this->loadMerchantAuthority(); diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantListController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantListController.php index 48fb02195b..87cfaaf880 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantListController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantListController.php @@ -1,37 +1,12 @@ getViewer(); - $querykey = $request->getURIData('queryKey'); - - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($querykey) - ->setSearchEngine(new PhortuneMerchantSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - - public function buildSideNavView() { - $viewer = $this->getViewer(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new PhortuneMerchantSearchEngine()) - ->setViewer($viewer) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; + return id(new PhortuneMerchantSearchEngine()) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantManagerController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantManagersController.php similarity index 73% rename from src/applications/phortune/controller/merchant/PhortuneMerchantManagerController.php rename to src/applications/phortune/controller/merchant/PhortuneMerchantManagersController.php index 0b9c0c6598..6b3ff568ff 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantManagerController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantManagersController.php @@ -1,35 +1,29 @@ getViewer(); - $id = $request->getURIData('id'); + $merchant = $this->getMerchant(); - $merchant = id(new PhortuneMerchantQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needProfileImage(true) - ->executeOne(); - if (!$merchant) { - return new Aphront404Response(); - } - - $this->setMerchant($merchant); - $header = $this->buildHeaderView(); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Managers')); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Managers')) + ->setBorder(true); $header = $this->buildHeaderView(); $members = $this->buildMembersSection($merchant); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFooter(array( - $members, - )); + ->setFooter( + array( + $members, + )); $navigation = $this->buildSideNavView('managers'); @@ -38,7 +32,6 @@ final class PhortuneMerchantManagerController ->setCrumbs($crumbs) ->setNavigation($navigation) ->appendChild($view); - } private function buildMembersSection(PhortuneMerchant $merchant) { @@ -51,12 +44,18 @@ final class PhortuneMerchantManagerController $id = $merchant->getID(); + $add_uri = urisprintf( + 'merchant/%d/managers/new/', + $merchant->getID()); + $add_uri = $this->getApplicationURI($add_uri); + $add = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('New Manager')) ->setIcon('fa-plus') ->setWorkflow(true) - ->setHref("/phortune/merchant/manager/add/{$id}/"); + ->setDisabled(!$can_edit) + ->setHref($add_uri); $header = id(new PHUIHeaderView()) ->setHeader(pht('Merchant Account Managers')) diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantOrderListController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantOrderListController.php new file mode 100644 index 0000000000..5d33564ba7 --- /dev/null +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantOrderListController.php @@ -0,0 +1,55 @@ +getViewer(); + $merchant = $this->getMerchant(); + + $engine = id(new PhortuneCartSearchEngine()) + ->setController($this) + ->setMerchant($merchant); + + $subscription_id = $request->getURIData('subscriptionID'); + if ($subscription_id) { + $subscription = id(new PhortuneSubscriptionQuery()) + ->setViewer($viewer) + ->withIDs(array($subscription_id)) + ->executeOne(); + if (!$subscription) { + return new Aphront404Response(); + } + + $engine->setSubscription($subscription); + $this->subscription = $subscription; + } + + return $engine->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $subscription = $this->subscription; + if ($subscription) { + $crumbs->addTextCrumb( + $subscription->getObjectName(), + $subscription->getURI()); + } else if ($this->hasMerchant()) { + $merchant = $this->getMerchant(); + + $crumbs->addTextCrumb(pht('Orders'), $merchant->getOrdersURI()); + } + + return $crumbs; + } + + +} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantOrdersController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantOrdersController.php new file mode 100644 index 0000000000..4f84d0cce7 --- /dev/null +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantOrdersController.php @@ -0,0 +1,78 @@ +getMerchant(); + $title = $merchant->getName(); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Orders')) + ->setBorder(true); + + $header = $this->buildHeaderView(); + $order_history = $this->newRecentOrdersView($merchant, 100); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter( + array( + $order_history, + )); + + $navigation = $this->buildSideNavView('orders'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + } + + private function newRecentOrdersView( + PhortuneMerchant $merchant, + $limit) { + + $viewer = $this->getViewer(); + + $carts = id(new PhortuneCartQuery()) + ->setViewer($viewer) + ->withMerchantPHIDs(array($merchant->getPHID())) + ->needPurchases(true) + ->withStatuses( + array( + PhortuneCart::STATUS_PURCHASING, + PhortuneCart::STATUS_CHARGED, + PhortuneCart::STATUS_HOLD, + PhortuneCart::STATUS_REVIEW, + PhortuneCart::STATUS_PURCHASED, + )) + ->setLimit($limit) + ->execute(); + + $orders_uri = $merchant->getOrderListURI(); + + $table = id(new PhortuneOrderTableView()) + ->setUser($viewer) + ->setCarts($carts); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Recent Orders')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-list') + ->setHref($orders_uri) + ->setText(pht('View All Orders'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } +} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantOverviewController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantOverviewController.php new file mode 100644 index 0000000000..2925a7d34b --- /dev/null +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantOverviewController.php @@ -0,0 +1,136 @@ +getViewer(); + $merchant = $this->getMerchant(); + + $crumbs = $this->buildApplicationCrumbs() + ->setBorder(true); + + $header = $this->buildHeaderView(); + + $title = pht( + '%s %s', + $merchant->getObjectName(), + $merchant->getName()); + + $providers = id(new PhortunePaymentProviderConfigQuery()) + ->setViewer($viewer) + ->withMerchantPHIDs(array($merchant->getPHID())) + ->execute(); + + $details = $this->buildDetailsView($merchant, $providers); + $navigation = $this->buildSideNavView('overview'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter( + array( + $details, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + } + + private function buildDetailsView( + PhortuneMerchant $merchant, + array $providers) { + + $viewer = $this->getRequest()->getUser(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($merchant); + + $status_view = new PHUIStatusListView(); + + $have_any = false; + $any_test = false; + foreach ($providers as $provider_config) { + $provider = $provider_config->buildProvider(); + if ($provider->isEnabled()) { + $have_any = true; + } + if (!$provider->isAcceptingLivePayments()) { + $any_test = true; + } + } + + if ($have_any) { + $status_view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') + ->setTarget(pht('Accepts Payments')) + ->setNote(pht('This merchant can accept payments.'))); + + if ($any_test) { + $status_view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'yellow') + ->setTarget(pht('Test Mode')) + ->setNote(pht('This merchant is accepting test payments.'))); + } else { + $status_view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') + ->setTarget(pht('Live Mode')) + ->setNote(pht('This merchant is accepting live payments.'))); + } + } else if ($providers) { + $status_view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_REJECT, 'red') + ->setTarget(pht('No Enabled Providers')) + ->setNote( + pht( + 'All of the payment providers for this merchant are '. + 'disabled.'))); + } else { + $status_view->addItem( + id(new PHUIStatusItemView()) + ->setIcon(PHUIStatusItemView::ICON_WARNING, 'yellow') + ->setTarget(pht('No Providers')) + ->setNote( + pht( + 'This merchant does not have any payment providers configured '. + 'yet, so it can not accept payments. Add a provider.'))); + } + + $view->addProperty(pht('Status'), $status_view); + + $description = $merchant->getDescription(); + if (strlen($description)) { + $description = new PHUIRemarkupView($viewer, $description); + $view->addSectionHeader( + pht('Description'), + PHUIPropertyListView::ICON_SUMMARY); + $view->addTextContent($description); + } + + $contact_info = $merchant->getContactInfo(); + if (strlen($contact_info)) { + $contact_info = new PHUIRemarkupView($viewer, $contact_info); + $view->addSectionHeader( + pht('Contact Information'), + PHUIPropertyListView::ICON_SUMMARY); + $view->addTextContent($contact_info); + } + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); + } + +} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php index cbd122f682..96f4f8e367 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantPictureController.php @@ -1,28 +1,17 @@ getViewer(); - $id = $request->getURIData('id'); + $merchant = $this->getMerchant(); - $merchant = id(new PhortuneMerchantQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needProfileImage(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$merchant) { - return new Aphront404Response(); - } - - $this->setMerchant($merchant); - $uri = $merchant->getURI(); + $uri = $merchant->getDetailsURI(); $supported_formats = PhabricatorFile::getTransformableImageFormats(); $e_file = true; @@ -222,12 +211,9 @@ final class PhortuneMerchantPictureController $upload_box, )); - $navigation = $this->buildSideNavView(); - return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->setNavigation($navigation) ->appendChild( array( $view, diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantProfileController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantProfileController.php index 45911bfef9..bc54756ffe 100644 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantProfileController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantProfileController.php @@ -1,22 +1,7 @@ merchant = $merchant; - return $this; - } - - public function getMerchant() { - return $this->merchant; - } - - public function buildApplicationMenu() { - return $this->buildSideNavView()->getMenu(); - } + extends PhortuneMerchantController { protected function buildHeaderView() { $viewer = $this->getViewer(); @@ -26,20 +11,20 @@ abstract class PhortuneMerchantProfileController $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) - ->setPolicyObject($merchant) ->setImage($merchant->getProfileImageURI()); return $header; } protected function buildApplicationCrumbs() { - $merchant = $this->getMerchant(); - $id = $merchant->getID(); - $merchant_uri = $this->getApplicationURI("/merchant/{$id}/"); - $crumbs = parent::buildApplicationCrumbs(); - $crumbs->addTextCrumb($merchant->getName(), $merchant_uri); - $crumbs->setBorder(true); + + if ($this->hasMerchant()) { + $merchant = $this->getMerchant(); + $merchant_uri = $merchant->getURI(); + $crumbs->addTextCrumb($merchant->getName(), $merchant_uri); + } + return $crumbs; } @@ -58,31 +43,47 @@ abstract class PhortuneMerchantProfileController $nav->addLabel(pht('Merchant')); - $nav->addFilter( - 'overview', - pht('Overview'), - $this->getApplicationURI("/merchant/{$id}/"), - 'fa-building-o'); + $nav->newLink('overview') + ->setName(pht('Overview')) + ->setHref($merchant->getURI()) + ->setIcon('fa-building-o'); - if ($can_edit) { - $nav->addFilter( - 'orders', - pht('Orders'), - $this->getApplicationURI("merchant/orders/{$id}/"), - 'fa-retweet'); + $nav->newLink('details') + ->setName(pht('Account Details')) + ->setHref($merchant->getDetailsURI()) + ->setIcon('fa-address-card-o') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit); - $nav->addFilter( - 'subscriptions', - pht('Subscriptions'), - $this->getApplicationURI("merchant/{$id}/subscription/"), - 'fa-shopping-cart'); + $nav->addLabel(pht('Payments')); - $nav->addFilter( - 'managers', - pht('Managers'), - $this->getApplicationURI("/merchant/manager/{$id}/"), - 'fa-group'); - } + $nav->newLink('providers') + ->setName(pht('Payment Providers')) + ->setHref($merchant->getPaymentProvidersURI()) + ->setIcon('fa-credit-card') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit); + + $nav->newLink('orders') + ->setName(pht('Orders')) + ->setHref($merchant->getOrdersURI()) + ->setIcon('fa-shopping-bag') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit); + + $nav->newLink('subscriptions') + ->setName(pht('Subscriptions')) + ->setHref($merchant->getSubscriptionsURI()) + ->setIcon('fa-retweet') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit); + + $nav->addLabel(pht('Personnel')); + + $nav->newLink('managers') + ->setName(pht('Managers')) + ->setHref($merchant->getManagersURI()) + ->setIcon('fa-group'); $nav->selectFilter($filter); diff --git a/src/applications/phortune/controller/provider/PhortuneProviderDisableController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantProviderDisableController.php similarity index 84% rename from src/applications/phortune/controller/provider/PhortuneProviderDisableController.php rename to src/applications/phortune/controller/merchant/PhortuneMerchantProviderDisableController.php index 03236b54bc..01928fca1b 100644 --- a/src/applications/phortune/controller/provider/PhortuneProviderDisableController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantProviderDisableController.php @@ -1,11 +1,17 @@ getViewer(); - $id = $request->getURIData('id'); + $merchant = $this->getMerchant(); + + $id = $request->getURIData('providerID'); $provider_config = id(new PhortunePaymentProviderConfigQuery()) ->setViewer($viewer) @@ -20,9 +26,8 @@ final class PhortuneProviderDisableController return new Aphront404Response(); } - $merchant = $provider_config->getMerchant(); $merchant_id = $merchant->getID(); - $cancel_uri = $this->getApplicationURI("merchant/{$merchant_id}/"); + $cancel_uri = $provider_config->getURI(); $provider = $provider_config->buildProvider(); diff --git a/src/applications/phortune/controller/provider/PhortuneProviderEditController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantProviderEditController.php similarity index 87% rename from src/applications/phortune/controller/provider/PhortuneProviderEditController.php rename to src/applications/phortune/controller/merchant/PhortuneMerchantProviderEditController.php index f7ad2486c4..bd577ecfb5 100644 --- a/src/applications/phortune/controller/provider/PhortuneProviderEditController.php +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantProviderEditController.php @@ -1,16 +1,23 @@ getViewer(); + $merchant = $this->getMerchant(); + $id = $request->getURIData('id'); if ($id) { $provider_config = id(new PhortunePaymentProviderConfigQuery()) ->setViewer($viewer) ->withIDs(array($id)) + ->withMerchantPHIDs(array($merchant->getPHID())) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -25,20 +32,8 @@ final class PhortuneProviderEditController $merchant = $provider_config->getMerchant(); $merchant_id = $merchant->getID(); - $cancel_uri = $this->getApplicationURI("merchant/{$merchant_id}/"); + $cancel_uri = $provider_config->getURI(); } else { - $merchant = id(new PhortuneMerchantQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getStr('merchantID'))) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$merchant) { - return new Aphront404Response(); - } $merchant_id = $merchant->getID(); $current_providers = id(new PhortunePaymentProviderConfigQuery()) @@ -62,9 +57,7 @@ final class PhortuneProviderEditController } $provider_config->setProviderClass($class); - - $cancel_uri = $this->getApplicationURI( - 'provider/edit/?merchantID='.$merchant_id); + $cancel_uri = $merchant->getPaymentProvidersURI(); } $provider = $provider_config->buildProvider(); @@ -123,10 +116,12 @@ final class PhortuneProviderEditController $xactions = array(); - $xactions[] = id(new PhortunePaymentProviderConfigTransaction()) - ->setTransactionType( - PhortunePaymentProviderConfigTransaction::TYPE_CREATE) - ->setNewValue(true); + if (!$provider_config->getID()) { + $xactions[] = id(new PhortunePaymentProviderConfigTransaction()) + ->setTransactionType( + PhortunePaymentProviderConfigTransaction::TYPE_CREATE) + ->setNewValue(true); + } foreach ($xaction_values as $key => $value) { $xactions[] = id(clone $template) @@ -143,9 +138,9 @@ final class PhortuneProviderEditController $editor->applyTransactions($provider_config, $xactions); - $merchant_uri = $this->getApplicationURI( - 'merchant/'.$merchant->getID().'/'); - return id(new AphrontRedirectResponse())->setURI($merchant_uri); + $next_uri = $provider_config->getURI(); + + return id(new AphrontRedirectResponse())->setURI($next_uri); } } } else { @@ -155,7 +150,6 @@ final class PhortuneProviderEditController $form = id(new AphrontFormView()) ->setUser($viewer) - ->addHiddenInput('merchantID', $merchant->getID()) ->addHiddenInput('class', $provider_config->getProviderClass()) ->addHiddenInput('edit', true) ->appendChild( @@ -261,7 +255,6 @@ final class PhortuneProviderEditController $form = id(new AphrontFormView()) ->setUser($viewer) - ->addHiddenInput('merchantID', $merchant->getID()) ->appendRemarkupInstructions( pht('Choose the type of payment provider to add:')) ->appendChild($panel_classes) diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantProviderViewController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantProviderViewController.php new file mode 100644 index 0000000000..2c1be9f8af --- /dev/null +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantProviderViewController.php @@ -0,0 +1,127 @@ +getViewer(); + $merchant = $this->getMerchant(); + + $provider = id(new PhortunePaymentProviderConfigQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('providerID'))) + ->executeOne(); + if (!$provider) { + return new Aphront404Response(); + } + + $provider_type = $provider->buildProvider(); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($merchant->getName(), $merchant->getURI()) + ->addTextCrumb( + pht('Payment Providers'), + $merchant->getPaymentProvidersURI()) + ->addTextCrumb($provider->getObjectName()) + ->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Payment Provider: %s', $provider_type->getName())); + + $details = $this->newDetailsView($provider); + + $timeline = $this->buildTransactionTimeline( + $provider, + new PhortunePaymentProviderConfigTransactionQuery()); + $timeline->setShouldTerminate(true); + + $curtain = $this->buildCurtainView($provider); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn( + array( + $details, + $timeline, + )); + + return $this->newPage() + ->setTitle($provider->getObjectName()) + ->setCrumbs($crumbs) + ->appendChild($view); + } + + private function buildCurtainView(PhortunePaymentProviderConfig $provider) { + $viewer = $this->getViewer(); + $merchant = $this->getMerchant(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $provider, + PhabricatorPolicyCapability::CAN_EDIT); + + $edit_uri = $this->getApplicationURI( + urisprintf( + 'merchant/%d/providers/edit/%d/', + $merchant->getID(), + $provider->getID())); + + $disable_uri = $this->getApplicationURI( + urisprintf( + 'merchant/%d/providers/%d/disable/', + $merchant->getID(), + $provider->getID())); + + $curtain = $this->newCurtainView($provider); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Provider')) + ->setIcon('fa-pencil') + ->setHref($edit_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + $provider_type = $provider->buildProvider(); + + if ($provider_type->isEnabled()) { + $disable_icon = 'fa-times'; + $disable_name = pht('Disable Provider'); + } else { + $disable_icon = 'fa-check'; + $disable_name = pht('Enable Provider'); + } + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName($disable_name) + ->setIcon($disable_icon) + ->setHref($disable_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); + + return $curtain; + } + + private function newDetailsView(PhortunePaymentProviderConfig $provider) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $provider_type = $provider->buildProvider(); + + $view->addProperty(pht('Provider Type'), $provider_type->getName()); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Payment Provider Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addPropertyList($view); + } + +} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantProvidersController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantProvidersController.php new file mode 100644 index 0000000000..2d64bdc726 --- /dev/null +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantProvidersController.php @@ -0,0 +1,116 @@ +getViewer(); + $merchant = $this->getMerchant(); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Payment Providers')) + ->setBorder(true); + + $header = $this->buildHeaderView(); + + $title = pht( + '%s %s', + $merchant->getObjectName(), + $merchant->getName()); + + $providers = id(new PhortunePaymentProviderConfigQuery()) + ->setViewer($viewer) + ->withMerchantPHIDs(array($merchant->getPHID())) + ->execute(); + + $provider_list = $this->buildProviderList( + $merchant, + $providers); + + $navigation = $this->buildSideNavView('providers'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter( + array( + $provider_list, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + } + + private function buildProviderList( + PhortuneMerchant $merchant, + array $providers) { + + $viewer = $this->getRequest()->getUser(); + $id = $merchant->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $merchant, + PhabricatorPolicyCapability::CAN_EDIT); + + $provider_list = id(new PHUIObjectItemListView()) + ->setNoDataString(pht('This merchant has no payment providers.')); + + foreach ($providers as $provider_config) { + $provider = $provider_config->buildProvider(); + $provider_id = $provider_config->getID(); + + $item = id(new PHUIObjectItemView()) + ->setObjectName($provider_config->getObjectName()) + ->setHeader($provider->getName()) + ->setHref($provider_config->getURI()); + + if ($provider->isEnabled()) { + if ($provider->isAcceptingLivePayments()) { + $item->setStatusIcon('fa-check green'); + } else { + $item->setStatusIcon('fa-warning yellow'); + $item->addIcon('fa-exclamation-triangle', pht('Test Mode')); + } + + $item->addAttribute($provider->getConfigureProvidesDescription()); + } else { + $item->setDisabled(true); + $item->addAttribute( + phutil_tag('em', array(), pht('This payment provider is disabled.'))); + } + + $provider_list->addItem($item); + } + + $add_uri = urisprintf( + 'merchant/%d/providers/edit/', + $merchant->getID()); + $add_uri = $this->getApplicationURI($add_uri); + + $add_action = id(new PHUIButtonView()) + ->setTag('a') + ->setHref($add_uri) + ->setText(pht('Add Payment Provider')) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setIcon('fa-plus'); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Payment Providers')) + ->addActionLink($add_action); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($provider_list); + } + + +} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantSubscriptionListController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantSubscriptionListController.php new file mode 100644 index 0000000000..d5a8d4eef2 --- /dev/null +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantSubscriptionListController.php @@ -0,0 +1,50 @@ +getViewer(); + $merchant = $this->getMerchant(); + + $engine = id(new PhortuneCartSearchEngine()) + ->setController($this) + ->setMerchant($merchant); + + $subscription_id = $request->getURIData('subscriptionID'); + if ($subscription_id) { + $subscription = id(new PhortuneSubscriptionQuery()) + ->setViewer($viewer) + ->withIDs(array($subscription_id)) + ->executeOne(); + if (!$subscription) { + return new Aphront404Response(); + } + + $engine->setSubscription($subscription); + $this->subscription = $subscription; + } + + return $engine->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + if ($this->hasMerchant()) { + $merchant = $this->getMerchant(); + + $crumbs->addTextCrumb( + pht('Subscriptions'), + $merchant->getSubscriptionsURI()); + } + + return $crumbs; + } + + +} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantSubscriptionsController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantSubscriptionsController.php new file mode 100644 index 0000000000..c2fe0d429a --- /dev/null +++ b/src/applications/phortune/controller/merchant/PhortuneMerchantSubscriptionsController.php @@ -0,0 +1,68 @@ +getMerchant(); + $title = $merchant->getName(); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Subscriptions')) + ->setBorder(true); + + $header = $this->buildHeaderView(); + + $subscriptions = $this->buildSubscriptionsSection($merchant); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter( + array( + $subscriptions, + )); + + $navigation = $this->buildSideNavView('subscriptions'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($navigation) + ->appendChild($view); + } + + private function buildSubscriptionsSection(PhortuneMerchant $merchant) { + $viewer = $this->getViewer(); + + $subscriptions = id(new PhortuneSubscriptionQuery()) + ->setViewer($viewer) + ->withMerchantPHIDs(array($merchant->getPHID())) + ->setLimit(25) + ->execute(); + + $subscriptions_uri = $merchant->getSubscriptionListURI(); + + $table = id(new PhortuneSubscriptionTableView()) + ->setUser($viewer) + ->setSubscriptions($subscriptions); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Subscriptions')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-list') + ->setHref($subscriptions_uri) + ->setText(pht('View All Subscriptions'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } + +} diff --git a/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php b/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php deleted file mode 100644 index 10dee4b0ea..0000000000 --- a/src/applications/phortune/controller/merchant/PhortuneMerchantViewController.php +++ /dev/null @@ -1,324 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $merchant = id(new PhortuneMerchantQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needProfileImage(true) - ->executeOne(); - if (!$merchant) { - return new Aphront404Response(); - } - - $this->setMerchant($merchant); - $crumbs = $this->buildApplicationCrumbs(); - $header = $this->buildHeaderView(); - - $title = pht( - 'Merchant %d %s', - $merchant->getID(), - $merchant->getName()); - - $providers = id(new PhortunePaymentProviderConfigQuery()) - ->setViewer($viewer) - ->withMerchantPHIDs(array($merchant->getPHID())) - ->execute(); - - $details = $this->buildDetailsView($merchant, $providers); - $curtain = $this->buildCurtainView($merchant); - - $provider_list = $this->buildProviderList( - $merchant, - $providers); - - $timeline = $this->buildTransactionTimeline( - $merchant, - new PhortuneMerchantTransactionQuery()); - $timeline->setShouldTerminate(true); - - $navigation = $this->buildSideNavView('overview'); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setCurtain($curtain) - ->setMainColumn(array( - $details, - $provider_list, - $timeline, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->setNavigation($navigation) - ->appendChild($view); - } - - private function buildDetailsView( - PhortuneMerchant $merchant, - array $providers) { - - $viewer = $this->getRequest()->getUser(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($merchant); - - $status_view = new PHUIStatusListView(); - - $have_any = false; - $any_test = false; - foreach ($providers as $provider_config) { - $provider = $provider_config->buildProvider(); - if ($provider->isEnabled()) { - $have_any = true; - } - if (!$provider->isAcceptingLivePayments()) { - $any_test = true; - } - } - - if ($have_any) { - $status_view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Accepts Payments')) - ->setNote(pht('This merchant can accept payments.'))); - - if ($any_test) { - $status_view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'yellow') - ->setTarget(pht('Test Mode')) - ->setNote(pht('This merchant is accepting test payments.'))); - } else { - $status_view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') - ->setTarget(pht('Live Mode')) - ->setNote(pht('This merchant is accepting live payments.'))); - } - } else if ($providers) { - $status_view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_REJECT, 'red') - ->setTarget(pht('No Enabled Providers')) - ->setNote( - pht( - 'All of the payment providers for this merchant are '. - 'disabled.'))); - } else { - $status_view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'yellow') - ->setTarget(pht('No Providers')) - ->setNote( - pht( - 'This merchant does not have any payment providers configured '. - 'yet, so it can not accept payments. Add a provider.'))); - } - - $view->addProperty(pht('Status'), $status_view); - - $invoice_from = $merchant->getInvoiceEmail(); - if (!$invoice_from) { - $invoice_from = pht('No email address set'); - $invoice_from = phutil_tag('em', array(), $invoice_from); - } - $view->addProperty(pht('Invoice From'), $invoice_from); - - $description = $merchant->getDescription(); - if (strlen($description)) { - $description = new PHUIRemarkupView($viewer, $description); - $view->addSectionHeader( - pht('Description'), - PHUIPropertyListView::ICON_SUMMARY); - $view->addTextContent($description); - } - - $contact_info = $merchant->getContactInfo(); - if (strlen($contact_info)) { - $contact_info = new PHUIRemarkupView($viewer, $contact_info); - $view->addSectionHeader( - pht('Contact Info'), - PHUIPropertyListView::ICON_SUMMARY); - $view->addTextContent($contact_info); - } - - $footer_info = $merchant->getInvoiceFooter(); - if (strlen($footer_info)) { - $footer_info = new PHUIRemarkupView($viewer, $footer_info); - $view->addSectionHeader( - pht('Invoice Footer'), - PHUIPropertyListView::ICON_SUMMARY); - $view->addTextContent($footer_info); - } - - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($view); - } - - private function buildCurtainView(PhortuneMerchant $merchant) { - $viewer = $this->getRequest()->getUser(); - $id = $merchant->getID(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $merchant, - PhabricatorPolicyCapability::CAN_EDIT); - - $curtain = $this->newCurtainView($merchant); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Merchant')) - ->setIcon('fa-pencil') - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit) - ->setHref($this->getApplicationURI("merchant/edit/{$id}/"))); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Logo')) - ->setIcon('fa-camera') - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit) - ->setHref($this->getApplicationURI("merchant/picture/{$id}/"))); - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('New Invoice')) - ->setIcon('fa-fax') - ->setHref($this->getApplicationURI("merchant/{$id}/invoice/new/")) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - - $member_phids = $merchant->getMemberPHIDs(); - $handles = $viewer->loadHandles($member_phids); - - $member_list = id(new PHUIObjectItemListView()) - ->setSimple(true); - - foreach ($member_phids as $member_phid) { - $image_uri = $handles[$member_phid]->getImageURI(); - $image_href = $handles[$member_phid]->getURI(); - $person = $handles[$member_phid]; - - $member = id(new PHUIObjectItemView()) - ->setImageURI($image_uri) - ->setHref($image_href) - ->setHeader($person->getFullName()); - - $member_list->addItem($member); - } - - $curtain->newPanel() - ->setHeaderText(pht('Managers')) - ->appendChild($member_list); - - return $curtain; - } - - private function buildProviderList( - PhortuneMerchant $merchant, - array $providers) { - - $viewer = $this->getRequest()->getUser(); - $id = $merchant->getID(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $merchant, - PhabricatorPolicyCapability::CAN_EDIT); - - $provider_list = id(new PHUIObjectItemListView()) - ->setFlush(true) - ->setNoDataString(pht('This merchant has no payment providers.')); - - foreach ($providers as $provider_config) { - $provider = $provider_config->buildProvider(); - $provider_id = $provider_config->getID(); - - $item = id(new PHUIObjectItemView()) - ->setHeader($provider->getName()); - - if ($provider->isEnabled()) { - if ($provider->isAcceptingLivePayments()) { - $item->setStatusIcon('fa-check green'); - } else { - $item->setStatusIcon('fa-warning yellow'); - $item->addIcon('fa-exclamation-triangle', pht('Test Mode')); - } - - $item->addAttribute($provider->getConfigureProvidesDescription()); - } else { - // Don't show disabled providers to users who can't manage the merchant - // account. - if (!$can_edit) { - continue; - } - $item->setDisabled(true); - $item->addAttribute( - phutil_tag('em', array(), pht('This payment provider is disabled.'))); - } - - - if ($can_edit) { - $edit_uri = $this->getApplicationURI( - "/provider/edit/{$provider_id}/"); - $disable_uri = $this->getApplicationURI( - "/provider/disable/{$provider_id}/"); - - if ($provider->isEnabled()) { - $disable_icon = 'fa-times'; - $disable_name = pht('Disable'); - } else { - $disable_icon = 'fa-check'; - $disable_name = pht('Enable'); - } - - $item->addAction( - id(new PHUIListItemView()) - ->setIcon($disable_icon) - ->setHref($disable_uri) - ->setName($disable_name) - ->setWorkflow(true)); - - $item->addAction( - id(new PHUIListItemView()) - ->setIcon('fa-pencil') - ->setHref($edit_uri) - ->setName(pht('Edit'))); - } - - $provider_list->addItem($item); - } - - $add_action = id(new PHUIButtonView()) - ->setTag('a') - ->setHref($this->getApplicationURI('provider/edit/?merchantID='.$id)) - ->setText(pht('Add Payment Provider')) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit) - ->setIcon('fa-plus'); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Payment Providers')) - ->addActionLink($add_action); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setObjectList($provider_list); - } - - -} diff --git a/src/applications/phortune/editor/PhortuneMerchantEditEngine.php b/src/applications/phortune/editor/PhortuneMerchantEditEngine.php index 04e07401c7..e09fba99ab 100644 --- a/src/applications/phortune/editor/PhortuneMerchantEditEngine.php +++ b/src/applications/phortune/editor/PhortuneMerchantEditEngine.php @@ -58,7 +58,7 @@ final class PhortuneMerchantEditEngine } protected function getObjectViewURI($object) { - return $object->getURI(); + return $object->getDetailsURI(); } public function isEngineConfigurable() { diff --git a/src/applications/phortune/editor/PhortuneMerchantEditor.php b/src/applications/phortune/editor/PhortuneMerchantEditor.php index 954570be3f..79fc7d534e 100644 --- a/src/applications/phortune/editor/PhortuneMerchantEditor.php +++ b/src/applications/phortune/editor/PhortuneMerchantEditor.php @@ -18,7 +18,6 @@ final class PhortuneMerchantEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDGE; return $types; diff --git a/src/applications/phortune/provider/PhortuneTestPaymentProvider.php b/src/applications/phortune/provider/PhortuneTestPaymentProvider.php index 02d57e3b6a..8b8c731a84 100644 --- a/src/applications/phortune/provider/PhortuneTestPaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneTestPaymentProvider.php @@ -26,7 +26,7 @@ final class PhortuneTestPaymentProvider extends PhortunePaymentProvider { } public function getConfigureInstructions() { - return pht('This providers does not require any special configuration.'); + return pht('This provider does not require any special configuration.'); } public function canRunConfigurationTest() { diff --git a/src/applications/phortune/storage/PhortuneMerchant.php b/src/applications/phortune/storage/PhortuneMerchant.php index 830ff8e1d5..6e0bf81e22 100644 --- a/src/applications/phortune/storage/PhortuneMerchant.php +++ b/src/applications/phortune/storage/PhortuneMerchant.php @@ -6,7 +6,6 @@ final class PhortuneMerchant extends PhortuneDAO PhabricatorPolicyInterface { protected $name; - protected $viewPolicy; protected $description; protected $contactInfo; protected $invoiceEmail; @@ -18,7 +17,6 @@ final class PhortuneMerchant extends PhortuneDAO public static function initializeNewMerchant(PhabricatorUser $actor) { return id(new PhortuneMerchant()) - ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) ->attachMemberPHIDs(array()) ->setContactInfo('') ->setInvoiceEmail('') @@ -74,6 +72,49 @@ final class PhortuneMerchant extends PhortuneDAO return pht('Merchant %d', $this->getID()); } + public function getDetailsURI() { + return urisprintf( + '/phortune/merchant/%d/details/', + $this->getID()); + } + + public function getOrdersURI() { + return urisprintf( + '/phortune/merchant/%d/orders/', + $this->getID()); + } + + public function getOrderListURI($path = '') { + return urisprintf( + '/phortune/merchant/%d/orders/list/%s', + $this->getID(), + $path); + } + + public function getSubscriptionsURI() { + return urisprintf( + '/phortune/merchant/%d/subscriptions/', + $this->getID()); + } + + public function getSubscriptionListURI($path = '') { + return urisprintf( + '/phortune/merchant/%d/subscriptions/list/%s', + $this->getID(), + $path); + } + + public function getManagersURI() { + return urisprintf( + '/phortune/merchant/%d/managers/', + $this->getID()); + } + + public function getPaymentProvidersURI() { + return urisprintf( + '/phortune/merchant/%d/providers/', + $this->getID()); + } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -100,7 +141,7 @@ final class PhortuneMerchant extends PhortuneDAO public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: - return $this->getViewPolicy(); + return PhabricatorPolicies::getMostOpenPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return PhabricatorPolicies::POLICY_NOONE; } diff --git a/src/applications/phortune/storage/PhortunePaymentProviderConfig.php b/src/applications/phortune/storage/PhortunePaymentProviderConfig.php index 1e151b3fbb..985906519c 100644 --- a/src/applications/phortune/storage/PhortunePaymentProviderConfig.php +++ b/src/applications/phortune/storage/PhortunePaymentProviderConfig.php @@ -17,6 +17,7 @@ final class PhortunePaymentProviderConfig extends PhortuneDAO PhortuneMerchant $merchant) { return id(new PhortunePaymentProviderConfig()) ->setMerchantPHID($merchant->getPHID()) + ->attachMerchant($merchant) ->setIsEnabled(1); } @@ -75,6 +76,17 @@ final class PhortunePaymentProviderConfig extends PhortuneDAO ->setProviderConfig($this); } + public function getObjectName() { + return pht('Provider %d', $this->getID()); + } + + public function getURI() { + return urisprintf( + '/phortune/merchant/%d/providers/%d/', + $this->getMerchant()->getID(), + $this->getID()); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/phortune/view/PhortuneOrderTableView.php b/src/applications/phortune/view/PhortuneOrderTableView.php index 435f5151d0..b4b8866160 100644 --- a/src/applications/phortune/view/PhortuneOrderTableView.php +++ b/src/applications/phortune/view/PhortuneOrderTableView.php @@ -3,20 +3,10 @@ final class PhortuneOrderTableView extends AphrontView { private $carts; - private $handles; private $noDataString; private $isInvoices; private $isMerchantView; - public function setHandles(array $handles) { - $this->handles = $handles; - return $this; - } - - public function getHandles() { - return $this->handles; - } - public function setCarts(array $carts) { $this->carts = $carts; return $this; @@ -55,12 +45,22 @@ final class PhortuneOrderTableView extends AphrontView { public function render() { $carts = $this->getCarts(); - $handles = $this->getHandles(); $viewer = $this->getUser(); $is_invoices = $this->getIsInvoices(); $is_merchant = $this->getIsMerchantView(); + $phids = array(); + foreach ($carts as $cart) { + $phids[] = $cart->getPHID(); + foreach ($cart->getPurchases() as $purchase) { + $phids[] = $purchase->getPHID(); + } + $phids[] = $cart->getMerchantPHID(); + } + + $handles = $viewer->loadHandles($phids); + $rows = array(); $rowc = array(); foreach ($carts as $cart) { From b3f8045b873e042eece4c42f82cab278815ed287 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 22 Aug 2019 21:28:14 -0700 Subject: [PATCH 22/27] Make minor flavor updates Summary: Refresh the 404 text since it hasn't been updated in a while, and swap the "Save Query" button back to grey since I never got used to blue. Test Plan: Hit 404 page, saved a query. Differential Revision: https://secure.phabricator.com/D20734 --- src/aphront/response/Aphront404Response.php | 13 ++++++++----- .../PhabricatorApplicationSearchController.php | 3 ++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/aphront/response/Aphront404Response.php b/src/aphront/response/Aphront404Response.php index 1284cb62e6..ea98e9102b 100644 --- a/src/aphront/response/Aphront404Response.php +++ b/src/aphront/response/Aphront404Response.php @@ -8,16 +8,19 @@ final class Aphront404Response extends AphrontHTMLResponse { public function buildResponseString() { $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getViewer(); $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setViewer($viewer) ->setTitle(pht('404 Not Found')) - ->addCancelButton('/', pht('Focus')) + ->addCancelButton('/', pht('Return to Charted Waters')) ->appendParagraph( pht( - 'Do not dwell in the past, do not dream of the future, '. - 'concentrate the mind on the present moment.')); + 'You arrive at your destination, but there is nothing here.')) + ->appendParagraph( + pht( + 'Perhaps the real treasure was the friends you made '. + 'along the way.')); $view = id(new PhabricatorStandardPageView()) ->setTitle(pht('404 Not Found')) diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index 81373ee190..98d125fd33 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -194,9 +194,10 @@ final class PhabricatorApplicationSearchController if ($run_query && !$named_query && $user->isLoggedIn()) { $save_button = id(new PHUIButtonView()) ->setTag('a') + ->setColor(PHUIButtonView::GREY) ->setHref('/search/edit/key/'.$saved_query->getQueryKey().'/') ->setText(pht('Save Query')) - ->setIcon('fa-floppy-o'); + ->setIcon('fa-bookmark'); $submit->addButton($save_button); } From a39a37fc0e3768244bf4a4e5a6c56b8809c42164 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 22 Aug 2019 21:28:37 -0700 Subject: [PATCH 23/27] Update the Phortune cart/invoice workflow for policy changes Summary: Depends on D20734. Ref T13366. This makes the cart/order flow work under the new policy scheme with no "grantAuthority()" calls. It prepares for a "Void Invoice" action, although the action doesn't actually do anything yet. Test Plan: With and without merchant authority, viewed and paid invoices and went through the other invoice interaction workflows. Maniphest Tasks: T13366 Differential Revision: https://secure.phabricator.com/D20735 --- src/__phutil_library_map__.php | 6 +- .../PhabricatorPhortuneApplication.php | 2 + .../controller/PhortuneController.php | 62 ------ .../PhortuneAccountChargesController.php | 15 +- .../PhortuneAccountOrdersController.php | 2 +- .../PhortuneAccountProfileController.php | 4 +- .../cart/PhortuneCartAcceptController.php | 30 ++- .../cart/PhortuneCartCancelController.php | 31 ++- .../cart/PhortuneCartCheckoutController.php | 26 +-- .../cart/PhortuneCartController.php | 71 +++++++ .../cart/PhortuneCartUpdateController.php | 29 ++- .../cart/PhortuneCartViewController.php | 170 ++++++++-------- .../cart/PhortuneCartVoidController.php | 43 ++++ .../PhortuneSubscriptionEditController.php | 185 ------------------ .../PhortuneSubscriptionListController.php | 99 ---------- .../query/PhortuneCartSearchEngine.php | 2 +- .../query/PhortuneChargeSearchEngine.php | 19 +- .../PhortuneSubscriptionSearchEngine.php | 4 +- .../phortune/storage/PhortuneCart.php | 41 +++- .../phortune/view/PhortuneChargeTableView.php | 23 ++- 20 files changed, 300 insertions(+), 564 deletions(-) create mode 100644 src/applications/phortune/controller/cart/PhortuneCartVoidController.php delete mode 100644 src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php delete mode 100644 src/applications/phortune/controller/subscription/PhortuneSubscriptionListController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5fee952d0a..9e01eb700d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5279,6 +5279,7 @@ phutil_register_library_map(array( 'PhortuneCartTransactionQuery' => 'applications/phortune/query/PhortuneCartTransactionQuery.php', 'PhortuneCartUpdateController' => 'applications/phortune/controller/cart/PhortuneCartUpdateController.php', 'PhortuneCartViewController' => 'applications/phortune/controller/cart/PhortuneCartViewController.php', + 'PhortuneCartVoidController' => 'applications/phortune/controller/cart/PhortuneCartVoidController.php', 'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php', 'PhortuneChargePHIDType' => 'applications/phortune/phid/PhortuneChargePHIDType.php', 'PhortuneChargeQuery' => 'applications/phortune/query/PhortuneChargeQuery.php', @@ -5372,10 +5373,8 @@ phutil_register_library_map(array( 'PhortuneSubscription' => 'applications/phortune/storage/PhortuneSubscription.php', 'PhortuneSubscriptionAutopayTransaction' => 'applications/phortune/xaction/subscription/PhortuneSubscriptionAutopayTransaction.php', 'PhortuneSubscriptionCart' => 'applications/phortune/cart/PhortuneSubscriptionCart.php', - 'PhortuneSubscriptionEditController' => 'applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php', 'PhortuneSubscriptionEditor' => 'applications/phortune/editor/PhortuneSubscriptionEditor.php', 'PhortuneSubscriptionImplementation' => 'applications/phortune/subscription/PhortuneSubscriptionImplementation.php', - 'PhortuneSubscriptionListController' => 'applications/phortune/controller/subscription/PhortuneSubscriptionListController.php', 'PhortuneSubscriptionPHIDType' => 'applications/phortune/phid/PhortuneSubscriptionPHIDType.php', 'PhortuneSubscriptionPolicyCodex' => 'applications/phortune/codex/PhortuneSubscriptionPolicyCodex.php', 'PhortuneSubscriptionProduct' => 'applications/phortune/product/PhortuneSubscriptionProduct.php', @@ -11861,6 +11860,7 @@ phutil_register_library_map(array( 'PhortuneCartTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortuneCartUpdateController' => 'PhortuneCartController', 'PhortuneCartViewController' => 'PhortuneCartController', + 'PhortuneCartVoidController' => 'PhortuneCartController', 'PhortuneCharge' => array( 'PhortuneDAO', 'PhabricatorPolicyInterface', @@ -11984,10 +11984,8 @@ phutil_register_library_map(array( ), 'PhortuneSubscriptionAutopayTransaction' => 'PhortuneSubscriptionTransactionType', 'PhortuneSubscriptionCart' => 'PhortuneCartImplementation', - 'PhortuneSubscriptionEditController' => 'PhortuneController', 'PhortuneSubscriptionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneSubscriptionImplementation' => 'Phobject', - 'PhortuneSubscriptionListController' => 'PhortuneController', 'PhortuneSubscriptionPHIDType' => 'PhabricatorPHIDType', 'PhortuneSubscriptionPolicyCodex' => 'PhabricatorPolicyCodex', 'PhortuneSubscriptionProduct' => 'PhortuneProductImplementation', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 15eb500fc3..18a5d45f69 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -43,6 +43,8 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { 'checkout/' => 'PhortuneCartCheckoutController', '(?Pprint)/' => 'PhortuneCartViewController', '(?Pcancel|refund)/' => 'PhortuneCartCancelController', + 'accept/' => 'PhortuneCartAcceptController', + 'void/' => 'PhortuneCartVoidController', 'update/' => 'PhortuneCartUpdateController', ), 'account/' => array( diff --git a/src/applications/phortune/controller/PhortuneController.php b/src/applications/phortune/controller/PhortuneController.php index 655dcee4e1..37896cc941 100644 --- a/src/applications/phortune/controller/PhortuneController.php +++ b/src/applications/phortune/controller/PhortuneController.php @@ -2,42 +2,6 @@ abstract class PhortuneController extends PhabricatorController { - protected function addAccountCrumb( - $crumbs, - PhortuneAccount $account, - $link = true) { - - $name = $account->getName(); - $href = null; - - if ($link) { - $href = $this->getApplicationURI($account->getID().'/'); - $crumbs->addTextCrumb($name, $href); - } else { - $crumbs->addTextCrumb($name); - } - } - - protected function addMerchantCrumb( - $crumbs, - PhortuneMerchant $merchant, - $link = true) { - - $name = $merchant->getName(); - $href = null; - - $crumbs->addTextCrumb( - pht('Merchants'), - $this->getApplicationURI('merchant/')); - - if ($link) { - $href = $this->getApplicationURI('merchant/'.$merchant->getID().'/'); - $crumbs->addTextCrumb($name, $href); - } else { - $crumbs->addTextCrumb($name); - } - } - private function loadEnabledProvidersForMerchant(PhortuneMerchant $merchant) { $viewer = $this->getRequest()->getUser(); @@ -84,30 +48,4 @@ abstract class PhortuneController extends PhabricatorController { return $providers; } - protected function loadMerchantAuthority() { - $request = $this->getRequest(); - $viewer = $this->getViewer(); - - $is_merchant = (bool)$request->getURIData('merchantID'); - if (!$is_merchant) { - return null; - } - - $merchant = id(new PhortuneMerchantQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getURIData('merchantID'))) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$merchant) { - return null; - } - - $viewer->grantAuthority($merchant); - return $merchant; - } - } diff --git a/src/applications/phortune/controller/account/PhortuneAccountChargesController.php b/src/applications/phortune/controller/account/PhortuneAccountChargesController.php index db85698be7..ea6105504f 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountChargesController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountChargesController.php @@ -12,7 +12,7 @@ final class PhortuneAccountChargesController $title = $account->getName(); $crumbs = $this->buildApplicationCrumbs() - ->addTextCrumb(pht('Order History')) + ->addTextCrumb(pht('Orders')) ->setBorder(true); $header = $this->buildHeaderView(); @@ -46,22 +46,11 @@ final class PhortuneAccountChargesController ->setLimit(100) ->execute(); - $phids = array(); - foreach ($charges as $charge) { - $phids[] = $charge->getProviderPHID(); - $phids[] = $charge->getCartPHID(); - $phids[] = $charge->getMerchantPHID(); - $phids[] = $charge->getPaymentMethodPHID(); - } - - $handles = $this->loadViewerHandles($phids); - $charges_uri = $account->getChargeListURI(); $table = id(new PhortuneChargeTableView()) ->setUser($viewer) - ->setCharges($charges) - ->setHandles($handles); + ->setCharges($charges); $header = id(new PHUIHeaderView()) ->setHeader(pht('Recent Charges')) diff --git a/src/applications/phortune/controller/account/PhortuneAccountOrdersController.php b/src/applications/phortune/controller/account/PhortuneAccountOrdersController.php index 902d2032ff..6ca57453ed 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountOrdersController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountOrdersController.php @@ -12,7 +12,7 @@ final class PhortuneAccountOrdersController $title = $account->getName(); $crumbs = $this->buildApplicationCrumbs() - ->addTextCrumb(pht('Order History')) + ->addTextCrumb(pht('Orders')) ->setBorder(true); $header = $this->buildHeaderView(); diff --git a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php index 33361c1f66..212dfcd5a7 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountProfileController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountProfileController.php @@ -66,13 +66,13 @@ abstract class PhortuneAccountProfileController $nav->addFilter( 'orders', - pht('Order History'), + pht('Orders'), $account->getOrdersURI(), 'fa-shopping-bag'); $nav->addFilter( 'charges', - pht('Charge History'), + pht('Charges'), $account->getChargesURI(), 'fa-calculator'); diff --git a/src/applications/phortune/controller/cart/PhortuneCartAcceptController.php b/src/applications/phortune/controller/cart/PhortuneCartAcceptController.php index cb53e66f50..79969b999d 100644 --- a/src/applications/phortune/controller/cart/PhortuneCartAcceptController.php +++ b/src/applications/phortune/controller/cart/PhortuneCartAcceptController.php @@ -3,27 +3,19 @@ final class PhortuneCartAcceptController extends PhortuneCartController { - public function handleRequest(AphrontRequest $request) { + protected function shouldRequireAccountAuthority() { + return false; + } + + protected function shouldRequireMerchantAuthority() { + return true; + } + + protected function handleCartRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $id = $request->getURIData('id'); + $cart = $this->getCart(); - // You must control the merchant to accept orders. - $authority = $this->loadMerchantAuthority(); - if (!$authority) { - return new Aphront404Response(); - } - - $cart = id(new PhortuneCartQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->withMerchantPHIDs(array($authority->getPHID())) - ->needPurchases(true) - ->executeOne(); - if (!$cart) { - return new Aphront404Response(); - } - - $cancel_uri = $cart->getDetailURI($authority); + $cancel_uri = $cart->getDetailURI(); if ($cart->getStatus() !== PhortuneCart::STATUS_REVIEW) { return $this->newDialog() diff --git a/src/applications/phortune/controller/cart/PhortuneCartCancelController.php b/src/applications/phortune/controller/cart/PhortuneCartCancelController.php index c4a26c0d00..bad476ea1c 100644 --- a/src/applications/phortune/controller/cart/PhortuneCartCancelController.php +++ b/src/applications/phortune/controller/cart/PhortuneCartCancelController.php @@ -3,26 +3,21 @@ final class PhortuneCartCancelController extends PhortuneCartController { - public function handleRequest(AphrontRequest $request) { + protected function shouldRequireAccountAuthority() { + return false; + } + + protected function shouldRequireMerchantAuthority() { + return false; + } + + protected function handleCartRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); $action = $request->getURIData('action'); - $authority = $this->loadMerchantAuthority(); - - $cart_query = id(new PhortuneCartQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPurchases(true); - - if ($authority) { - $cart_query->withMerchantPHIDs(array($authority->getPHID())); - } - - $cart = $cart_query->executeOne(); - if (!$cart) { - return new Aphront404Response(); - } + $cart = $this->getCart(); + $authority = $this->getMerchantAuthority(); switch ($action) { case 'cancel': @@ -45,7 +40,7 @@ final class PhortuneCartCancelController return new Aphront404Response(); } - $cancel_uri = $cart->getDetailURI($authority); + $cancel_uri = $cart->getDetailURI(); $merchant = $cart->getMerchant(); try { @@ -60,7 +55,7 @@ final class PhortuneCartCancelController return $this->newDialog() ->setTitle($title) ->appendChild($ex->getMessage()) - ->addCancelButton($cancel_uri); + ->addCancelButton($cancel_uri, pht('Rats')); } $charges = id(new PhortuneChargeQuery()) diff --git a/src/applications/phortune/controller/cart/PhortuneCartCheckoutController.php b/src/applications/phortune/controller/cart/PhortuneCartCheckoutController.php index 874ecf63aa..4b59afef75 100644 --- a/src/applications/phortune/controller/cart/PhortuneCartCheckoutController.php +++ b/src/applications/phortune/controller/cart/PhortuneCartCheckoutController.php @@ -3,18 +3,17 @@ final class PhortuneCartCheckoutController extends PhortuneCartController { - public function handleRequest(AphrontRequest $request) { - $viewer = $request->getViewer(); - $id = $request->getURIData('id'); + protected function shouldRequireAccountAuthority() { + return true; + } - $cart = id(new PhortuneCartQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPurchases(true) - ->executeOne(); - if (!$cart) { - return new Aphront404Response(); - } + protected function shouldRequireMerchantAuthority() { + return false; + } + + protected function handleCartRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $cart = $this->getCart(); $cancel_uri = $cart->getCancelURI(); $merchant = $cart->getMerchant(); @@ -139,7 +138,10 @@ final class PhortuneCartCheckoutController 'cartID' => $cart->getID(), ); - $payment_method_uri = $this->getApplicationURI("{$account_id}/card/new/"); + $payment_method_uri = urisprintf( + 'account/%d/methods/new/', + $account->getID()); + $payment_method_uri = $this->getApplicationURI($payment_method_uri); $payment_method_uri = new PhutilURI($payment_method_uri, $params); $form = id(new AphrontFormView()) diff --git a/src/applications/phortune/controller/cart/PhortuneCartController.php b/src/applications/phortune/controller/cart/PhortuneCartController.php index b8f926d3b2..2f5f55014e 100644 --- a/src/applications/phortune/controller/cart/PhortuneCartController.php +++ b/src/applications/phortune/controller/cart/PhortuneCartController.php @@ -3,6 +3,77 @@ abstract class PhortuneCartController extends PhortuneController { + private $cart; + private $merchantAuthority; + + abstract protected function shouldRequireAccountAuthority(); + abstract protected function shouldRequireMerchantAuthority(); + abstract protected function handleCartRequest(AphrontRequest $request); + + final public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + + if ($this->shouldRequireAccountAuthority()) { + $capabilities = array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } else { + $capabilities = array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + $cart = id(new PhortuneCartQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->needPurchases(true) + ->requireCapabilities($capabilities) + ->executeOne(); + if (!$cart) { + return new Aphront404Response(); + } + + if ($this->shouldRequireMerchantAuthority()) { + PhabricatorPolicyFilter::requireCapability( + $viewer, + $cart->getMerchant(), + PhabricatorPolicyCapability::CAN_EDIT); + } + + $this->cart = $cart; + + $can_edit = PhortuneMerchantQuery::canViewersEditMerchants( + array($viewer->getPHID()), + array($cart->getMerchantPHID())); + if ($can_edit) { + $this->merchantAuthority = $cart->getMerchant(); + } else { + $this->merchantAuthority = null; + } + + return $this->handleCartRequest($request); + } + + final protected function getCart() { + return $this->cart; + } + + final protected function getMerchantAuthority() { + return $this->merchantAuthority; + } + + final protected function hasMerchantAuthority() { + return (bool)$this->merchantAuthority; + } + + final protected function hasAccountAuthority() { + return (bool)PhabricatorPolicyFilter::hasCapability( + $this->getViewer(), + $this->getCart(), + PhabricatorPolicyCapability::CAN_EDIT); + } + protected function buildCartContentTable(PhortuneCart $cart) { $rows = array(); diff --git a/src/applications/phortune/controller/cart/PhortuneCartUpdateController.php b/src/applications/phortune/controller/cart/PhortuneCartUpdateController.php index 3d49611d2d..64b2ab3cfa 100644 --- a/src/applications/phortune/controller/cart/PhortuneCartUpdateController.php +++ b/src/applications/phortune/controller/cart/PhortuneCartUpdateController.php @@ -3,25 +3,20 @@ final class PhortuneCartUpdateController extends PhortuneCartController { - public function handleRequest(AphrontRequest $request) { + protected function shouldRequireAccountAuthority() { + return false; + } + + protected function shouldRequireMerchantAuthority() { + return false; + } + + protected function handleCartRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); - $authority = $this->loadMerchantAuthority(); - - $cart_query = id(new PhortuneCartQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPurchases(true); - - if ($authority) { - $cart_query->withMerchantPHIDs(array($authority->getPHID())); - } - - $cart = $cart_query->executeOne(); - if (!$cart) { - return new Aphront404Response(); - } + $cart = $this->getCart(); + $authority = $this->getMerchantAuthority(); $charges = id(new PhortuneChargeQuery()) ->setViewer($viewer) @@ -60,7 +55,7 @@ final class PhortuneCartUpdateController } return id(new AphrontRedirectResponse()) - ->setURI($cart->getDetailURI($authority)); + ->setURI($cart->getDetailURI()); } } diff --git a/src/applications/phortune/controller/cart/PhortuneCartViewController.php b/src/applications/phortune/controller/cart/PhortuneCartViewController.php index 8108c83b39..d728a202c9 100644 --- a/src/applications/phortune/controller/cart/PhortuneCartViewController.php +++ b/src/applications/phortune/controller/cart/PhortuneCartViewController.php @@ -5,62 +5,33 @@ final class PhortuneCartViewController private $action = null; - public function handleRequest(AphrontRequest $request) { + protected function shouldRequireAccountAuthority() { + return false; + } + + protected function shouldRequireMerchantAuthority() { + return false; + } + + protected function handleCartRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $id = $request->getURIData('id'); + $cart = $this->getCart(); + $authority = $this->getMerchantAuthority(); + $can_edit = $this->hasAccountAuthority(); + $this->action = $request->getURIData('action'); - $authority = $this->loadMerchantAuthority(); - require_celerity_resource('phortune-css'); - - $query = id(new PhortuneCartQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needPurchases(true); - - if ($authority) { - $query->withMerchantPHIDs(array($authority->getPHID())); - } - - $cart = $query->executeOne(); - if (!$cart) { - return new Aphront404Response(); - } - $cart_table = $this->buildCartContentTable($cart); - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $cart, - PhabricatorPolicyCapability::CAN_EDIT); - $errors = array(); $error_view = null; $resume_uri = null; switch ($cart->getStatus()) { case PhortuneCart::STATUS_READY: - if ($authority && $cart->getIsInvoice()) { - // We arrived here by following the ad-hoc invoice workflow, and - // are acting with merchant authority. - - $checkout_uri = PhabricatorEnv::getURI($cart->getCheckoutURI()); - - $invoice_message = array( - pht( - 'Manual invoices do not automatically notify recipients yet. '. - 'Send the payer this checkout link:'), - ' ', - phutil_tag( - 'a', - array( - 'href' => $checkout_uri, - ), - $checkout_uri), - ); - + if ($cart->getIsInvoice()) { $error_view = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING) - ->setErrors(array($invoice_message)); + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild(pht('This invoice is ready for payment.')); } break; case PhortuneCart::STATUS_PURCHASING: @@ -133,7 +104,7 @@ final class PhortuneCartViewController $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($cart->getName()) - ->setHeaderIcon('fa-shopping-cart'); + ->setHeaderIcon('fa-shopping-bag'); if ($cart->getStatus() == PhortuneCart::STATUS_PURCHASED) { $done_uri = $cart->getDoneURI(); @@ -160,18 +131,8 @@ final class PhortuneCartViewController ->needCarts(true) ->execute(); - $phids = array(); - foreach ($charges as $charge) { - $phids[] = $charge->getProviderPHID(); - $phids[] = $charge->getCartPHID(); - $phids[] = $charge->getMerchantPHID(); - $phids[] = $charge->getPaymentMethodPHID(); - } - $handles = $this->loadViewerHandles($phids); - $charges_table = id(new PhortuneChargeTableView()) ->setUser($viewer) - ->setHandles($handles) ->setCharges($charges) ->setShowOrder(false); @@ -182,14 +143,13 @@ final class PhortuneCartViewController $account = $cart->getAccount(); - $crumbs = $this->buildApplicationCrumbs(); - if ($authority) { - $this->addMerchantCrumb($crumbs, $authority); - } else { - $this->addAccountCrumb($crumbs, $cart->getAccount()); - } - $crumbs->addTextCrumb(pht('Cart %d', $cart->getID())); - $crumbs->setBorder(true); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($account->getName(), $account->getURI()) + ->addTextCrumb(pht('Orders'), $account->getOrdersURI()) + ->addTextCrumb(pht('Cart %d', $cart->getID())) + ->setBorder(true); + + require_celerity_resource('phortune-css'); if (!$this->action) { $class = 'phortune-cart-page'; @@ -267,6 +227,7 @@ final class PhortuneCartViewController if ($crumbs) { $page->setCrumbs($crumbs); } + return $page; } @@ -318,20 +279,31 @@ final class PhortuneCartViewController $viewer = $this->getViewer(); $id = $cart->getID(); $curtain = $this->newCurtainView($cart); + $status = $cart->getStatus(); + + $is_ready = ($status === PhortuneCart::STATUS_READY); $can_cancel = ($can_edit && $cart->canCancelOrder()); + $can_checkout = ($can_edit && $is_ready); + $can_accept = ($status === PhortuneCart::STATUS_REVIEW); + $can_refund = ($authority && $cart->canRefundOrder()); + $can_void = ($authority && $cart->canVoidOrder()); - if ($authority) { - $prefix = 'merchant/'.$authority->getID().'/'; - } else { - $prefix = ''; - } + $cancel_uri = $this->getApplicationURI("cart/{$id}/cancel/"); + $refund_uri = $this->getApplicationURI("cart/{$id}/refund/"); + $update_uri = $this->getApplicationURI("cart/{$id}/update/"); + $accept_uri = $this->getApplicationURI("cart/{$id}/accept/"); + $print_uri = $this->getApplicationURI("cart/{$id}/print/"); + $checkout_uri = $cart->getCheckoutURI(); + $void_uri = $this->getApplicationURI("cart/{$id}/void/"); - $cancel_uri = $this->getApplicationURI("{$prefix}cart/{$id}/cancel/"); - $refund_uri = $this->getApplicationURI("{$prefix}cart/{$id}/refund/"); - $update_uri = $this->getApplicationURI("{$prefix}cart/{$id}/update/"); - $accept_uri = $this->getApplicationURI("{$prefix}cart/{$id}/accept/"); - $print_uri = $this->getApplicationURI("{$prefix}cart/{$id}/print/"); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Pay Now')) + ->setIcon('fa-credit-card') + ->setDisabled(!$can_checkout) + ->setWorkflow(!$can_checkout) + ->setHref($checkout_uri)); $curtain->addAction( id(new PhabricatorActionView()) @@ -341,24 +313,6 @@ final class PhortuneCartViewController ->setWorkflow(true) ->setHref($cancel_uri)); - if ($authority) { - if ($cart->getStatus() == PhortuneCart::STATUS_REVIEW) { - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Accept Order')) - ->setIcon('fa-check') - ->setWorkflow(true) - ->setHref($accept_uri)); - } - - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Refund Order')) - ->setIcon('fa-reply') - ->setWorkflow(true) - ->setHref($refund_uri)); - } - $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Update Status')) @@ -369,7 +323,7 @@ final class PhortuneCartViewController $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Continue Checkout')) - ->setIcon('fa-shopping-cart') + ->setIcon('fa-shopping-bag') ->setHref($resume_uri)); } @@ -380,6 +334,36 @@ final class PhortuneCartViewController ->setOpenInNewWindow(true) ->setIcon('fa-print')); + if ($authority) { + $curtain->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Accept Order')) + ->setIcon('fa-check') + ->setWorkflow(true) + ->setDisabled(!$can_accept) + ->setHref($accept_uri)); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Refund Order')) + ->setIcon('fa-reply') + ->setWorkflow(true) + ->setDisabled(!$can_refund) + ->setHref($refund_uri)); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Void Invoice')) + ->setIcon('fa-times') + ->setWorkflow(true) + ->setDisabled(!$can_void) + ->setHref($void_uri)); + } + return $curtain; } diff --git a/src/applications/phortune/controller/cart/PhortuneCartVoidController.php b/src/applications/phortune/controller/cart/PhortuneCartVoidController.php new file mode 100644 index 0000000000..184775b10d --- /dev/null +++ b/src/applications/phortune/controller/cart/PhortuneCartVoidController.php @@ -0,0 +1,43 @@ +getViewer(); + $cart = $this->getCart(); + + $cancel_uri = $cart->getDetailURI(); + + try { + $title = pht('Unable to Void Invoice'); + $cart->assertCanVoidOrder(); + } catch (Exception $ex) { + return $this->newDialog() + ->setTitle($title) + ->appendChild($ex->getMessage()) + ->addCancelButton($cancel_uri); + } + + if ($request->isFormPost()) { + return id(new AphrontRedirectResponse())->setURI($cancel_uri); + } + + return $this->newDialog() + ->setTitle(pht('Void Invoice?')) + ->appendParagraph( + pht( + 'Really void this invoice? The customer will no longer be asked '. + 'to submit payment for it.')) + ->addCancelButton($cancel_uri) + ->addSubmitButton(pht('Void Invoice')); + } +} diff --git a/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php b/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php deleted file mode 100644 index 04367a88a0..0000000000 --- a/src/applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php +++ /dev/null @@ -1,185 +0,0 @@ -getViewer(); - $added = $request->getBool('added'); - - $subscription = id(new PhortuneSubscriptionQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getURIData('id'))) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$subscription) { - return new Aphront404Response(); - } - - id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( - $viewer, - $request, - $subscription->getURI()); - $merchant = $subscription->getMerchant(); - $account = $subscription->getAccount(); - - $title = pht('Subscription: %s', $subscription->getSubscriptionName()); - - $header = id(new PHUIHeaderView()) - ->setHeader($subscription->getSubscriptionName()); - - $view_uri = $subscription->getURI(); - - $valid_methods = id(new PhortunePaymentMethodQuery()) - ->setViewer($viewer) - ->withAccountPHIDs(array($account->getPHID())) - ->withStatuses( - array( - PhortunePaymentMethod::STATUS_ACTIVE, - )) - ->withMerchantPHIDs(array($merchant->getPHID())) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->execute(); - $valid_methods = mpull($valid_methods, null, 'getPHID'); - - $current_phid = $subscription->getDefaultPaymentMethodPHID(); - - $e_method = null; - if ($current_phid && empty($valid_methods[$current_phid])) { - $e_method = pht('Needs Update'); - } - - $errors = array(); - if ($request->isFormPost()) { - - $default_method_phid = $request->getStr('defaultPaymentMethodPHID'); - if (!$default_method_phid) { - $default_method_phid = null; - $e_method = null; - } else if (empty($valid_methods[$default_method_phid])) { - $e_method = pht('Invalid'); - if ($default_method_phid == $current_phid) { - $errors[] = pht( - 'This subscription is configured to autopay with a payment method '. - 'that has been deleted. Choose a valid payment method or disable '. - 'autopay.'); - } else { - $errors[] = pht('You must select a valid default payment method.'); - } - } - - // TODO: We should use transactions here, and move the validation logic - // inside the Editor. - - if (!$errors) { - $subscription->setDefaultPaymentMethodPHID($default_method_phid); - $subscription->save(); - - return id(new AphrontRedirectResponse()) - ->setURI($view_uri); - } - } - - // Add the option to disable autopay. - $disable_options = array( - '' => pht('(Disable Autopay)'), - ); - - // Don't require the user to make a valid selection if the current method - // has become invalid. - if ($current_phid && empty($valid_methods[$current_phid])) { - $current_options = array( - $current_phid => pht(''), - ); - } else { - $current_options = array(); - } - - // Add any available options. - $valid_options = mpull($valid_methods, 'getFullDisplayName', 'getPHID'); - - $options = $disable_options + $current_options + $valid_options; - - $crumbs = $this->buildApplicationCrumbs(); - $this->addAccountCrumb($crumbs, $account); - $crumbs->addTextCrumb( - pht('Subscription %d', $subscription->getID()), - $view_uri); - $crumbs->addTextCrumb(pht('Edit')); - $crumbs->setBorder(true); - - - $uri = $this->getApplicationURI($account->getID().'/card/new/'); - $uri = new PhutilURI($uri); - $uri->replaceQueryParam('merchantID', $merchant->getID()); - $uri->replaceQueryParam('subscriptionID', $subscription->getID()); - - $add_method_button = phutil_tag( - 'a', - array( - 'href' => $uri, - 'class' => 'button button-grey', - ), - pht('Add Payment Method...')); - - $radio = id(new AphrontFormRadioButtonControl()) - ->setName('defaultPaymentMethodPHID') - ->setLabel(pht('Autopay With')) - ->setValue($current_phid) - ->setError($e_method); - - foreach ($options as $key => $value) { - $radio->addButton($key, $value, null); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild($radio) - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setValue($add_method_button)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save Changes')) - ->addCancelButton($view_uri)); - - $box = id(new PHUIObjectBoxView()) - ->setUser($viewer) - ->setHeaderText(pht('Subscription')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setFormErrors($errors) - ->appendChild($form); - - if ($added) { - $info_view = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_SUCCESS) - ->appendChild(pht('Payment method has been successfully added.')); - $box->setInfoView($info_view); - } - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Edit %s', $subscription->getSubscriptionName())) - ->setHeaderIcon('fa-pencil'); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( - $box, - )); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - - } - - -} diff --git a/src/applications/phortune/controller/subscription/PhortuneSubscriptionListController.php b/src/applications/phortune/controller/subscription/PhortuneSubscriptionListController.php deleted file mode 100644 index 469960f4bc..0000000000 --- a/src/applications/phortune/controller/subscription/PhortuneSubscriptionListController.php +++ /dev/null @@ -1,99 +0,0 @@ -getViewer(); - $querykey = $request->getURIData('queryKey'); - $merchant_id = $request->getURIData('merchantID'); - $account_id = $request->getURIData('accountID'); - - $engine = new PhortuneSubscriptionSearchEngine(); - - if ($merchant_id) { - $merchant = id(new PhortuneMerchantQuery()) - ->setViewer($viewer) - ->withIDs(array($merchant_id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$merchant) { - return new Aphront404Response(); - } - $this->merchant = $merchant; - $viewer->grantAuthority($merchant); - $engine->setMerchant($merchant); - } else if ($account_id) { - $account = id(new PhortuneAccountQuery()) - ->setViewer($viewer) - ->withIDs(array($account_id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$account) { - return new Aphront404Response(); - } - $this->account = $account; - $engine->setAccount($account); - } else { - return new Aphront404Response(); - } - - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($querykey) - ->setSearchEngine($engine) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - - public function buildSideNavView() { - $viewer = $this->getViewer(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new PhortuneSubscriptionSearchEngine()) - ->setViewer($viewer) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; - } - - protected function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - - $merchant = $this->merchant; - if ($merchant) { - $id = $merchant->getID(); - $this->addMerchantCrumb($crumbs, $merchant); - $crumbs->addTextCrumb( - pht('Subscriptions'), - $this->getApplicationURI("merchant/subscriptions/{$id}/")); - } - - $account = $this->account; - if ($account) { - $id = $account->getID(); - $this->addAccountCrumb($crumbs, $account); - $crumbs->addTextCrumb( - pht('Subscriptions'), - $this->getApplicationURI("{$id}/subscription/")); - } - - return $crumbs; - } - -} diff --git a/src/applications/phortune/query/PhortuneCartSearchEngine.php b/src/applications/phortune/query/PhortuneCartSearchEngine.php index da719c17c0..a4a0f2848d 100644 --- a/src/applications/phortune/query/PhortuneCartSearchEngine.php +++ b/src/applications/phortune/query/PhortuneCartSearchEngine.php @@ -105,7 +105,7 @@ final class PhortuneCartSearchEngine $merchant = $this->getMerchant(); $account = $this->getAccount(); if ($merchant) { - return '/phortune/merchant/orders/'.$merchant->getID().'/'.$path; + return $merchant->getOrderListURI($path); } else if ($account) { return $account->getOrderListURI($path); } else { diff --git a/src/applications/phortune/query/PhortuneChargeSearchEngine.php b/src/applications/phortune/query/PhortuneChargeSearchEngine.php index 45316118af..e1fb5a47ba 100644 --- a/src/applications/phortune/query/PhortuneChargeSearchEngine.php +++ b/src/applications/phortune/query/PhortuneChargeSearchEngine.php @@ -62,7 +62,7 @@ final class PhortuneChargeSearchEngine protected function getURI($path) { $account = $this->getAccount(); if ($account) { - return '/phortune/'.$account->getID().'/charge/'; + return $account->getChargeListURI($path); } else { return '/phortune/charge/'.$path; } @@ -89,20 +89,6 @@ final class PhortuneChargeSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } - protected function getRequiredHandlePHIDsForResultList( - array $charges, - PhabricatorSavedQuery $query) { - - $phids = array(); - foreach ($charges as $charge) { - $phids[] = $charge->getProviderPHID(); - $phids[] = $charge->getCartPHID(); - $phids[] = $charge->getMerchantPHID(); - $phids[] = $charge->getPaymentMethodPHID(); - } - - return $phids; - } protected function renderResultList( array $charges, @@ -114,8 +100,7 @@ final class PhortuneChargeSearchEngine $table = id(new PhortuneChargeTableView()) ->setUser($viewer) - ->setCharges($charges) - ->setHandles($handles); + ->setCharges($charges); $result = new PhabricatorApplicationSearchResultView(); $result->setTable($table); diff --git a/src/applications/phortune/query/PhortuneSubscriptionSearchEngine.php b/src/applications/phortune/query/PhortuneSubscriptionSearchEngine.php index 3f4bfb4e84..0d2e720aa7 100644 --- a/src/applications/phortune/query/PhortuneSubscriptionSearchEngine.php +++ b/src/applications/phortune/query/PhortuneSubscriptionSearchEngine.php @@ -96,9 +96,9 @@ final class PhortuneSubscriptionSearchEngine $merchant = $this->getMerchant(); $account = $this->getAccount(); if ($merchant) { - return '/phortune/merchant/'.$merchant->getID().'/subscription/'.$path; + return $merchant->getSubscriptionListURI($path); } else if ($account) { - return '/phortune/'.$account->getID().'/subscription/'; + return $account->getSubscriptionListURI($path); } else { return '/phortune/subscription/'.$path; } diff --git a/src/applications/phortune/storage/PhortuneCart.php b/src/applications/phortune/storage/PhortuneCart.php index f551cbb5fb..624a13d6b0 100644 --- a/src/applications/phortune/storage/PhortuneCart.php +++ b/src/applications/phortune/storage/PhortuneCart.php @@ -471,13 +471,10 @@ final class PhortuneCart extends PhortuneDAO return $this->getImplementation()->getDescription($this); } - public function getDetailURI(PhortuneMerchant $authority = null) { - if ($authority) { - $prefix = 'merchant/'.$authority->getID().'/'; - } else { - $prefix = ''; - } - return '/phortune/'.$prefix.'cart/'.$this->getID().'/'; + public function getDetailURI() { + return urisprintf( + '/phortune/cart/%d/', + $this->getID()); } public function getCheckoutURI() { @@ -502,6 +499,15 @@ final class PhortuneCart extends PhortuneDAO } } + public function canVoidOrder() { + try { + $this->assertCanVoidOrder(); + return true; + } catch (Exception $ex) { + return false; + } + } + public function assertCanCancelOrder() { switch ($this->getStatus()) { case self::STATUS_BUILDING: @@ -534,6 +540,27 @@ final class PhortuneCart extends PhortuneDAO return $this->getImplementation()->assertCanRefundOrder($this); } + public function assertCanVoidOrder() { + if (!$this->getIsInvoice()) { + throw new Exception( + pht( + 'This order can not be voided because it is not an invoice.')); + } + + switch ($this->getStatus()) { + case self::STATUS_READY: + break; + default: + throw new Exception( + pht( + 'This order can not be voided because it is not ready for '. + 'payment.')); + } + + return null; + } + + protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, diff --git a/src/applications/phortune/view/PhortuneChargeTableView.php b/src/applications/phortune/view/PhortuneChargeTableView.php index 663c470a81..1e8dd2cc9a 100644 --- a/src/applications/phortune/view/PhortuneChargeTableView.php +++ b/src/applications/phortune/view/PhortuneChargeTableView.php @@ -3,7 +3,6 @@ final class PhortuneChargeTableView extends AphrontView { private $charges; - private $handles; private $showOrder; public function setShowOrder($show_order) { @@ -15,15 +14,6 @@ final class PhortuneChargeTableView extends AphrontView { return $this->showOrder; } - public function setHandles(array $handles) { - $this->handles = $handles; - return $this; - } - - public function getHandles() { - return $this->handles; - } - public function setCharges(array $charges) { $this->charges = $charges; return $this; @@ -35,8 +25,17 @@ final class PhortuneChargeTableView extends AphrontView { public function render() { $charges = $this->getCharges(); - $handles = $this->getHandles(); - $viewer = $this->getUser(); + $viewer = $this->getViewer(); + + $phids = array(); + foreach ($charges as $charge) { + $phids[] = $charge->getCartPHID(); + $phids[] = $charge->getProviderPHID(); + $phids[] = $charge->getPaymentMethodPHID(); + $phids[] = $charge->getMerchantPHID(); + } + + $handles = $viewer->loadHandles($phids); $rows = array(); foreach ($charges as $charge) { From 8f6a1ab015949d88977de7d260fd9dca99e34333 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 23 Aug 2019 08:07:41 -0700 Subject: [PATCH 24/27] Roughly support external/email user views of Phortune recipts and invoices Summary: Ref T13366. This gives each account email address an "external portal" section so they can access invoices and receipts without an account. Test Plan: Viewed portal as user with authority and in an incognito window. Maniphest Tasks: T13366 Differential Revision: https://secure.phabricator.com/D20737 --- src/__phutil_library_map__.php | 4 + .../PhabricatorPhortuneApplication.php | 3 + .../PhortuneAccountEmailViewController.php | 14 ++ .../external/PhortuneExternalController.php | 147 ++++++++++++++++++ .../PhortuneExternalOverviewController.php | 91 +++++++++++ .../query/PhortuneAccountEmailQuery.php | 13 ++ .../phortune/storage/PhortuneAccountEmail.php | 7 + .../phortune/view/PhortuneOrderTableView.php | 24 +-- 8 files changed, 293 insertions(+), 10 deletions(-) create mode 100644 src/applications/phortune/controller/external/PhortuneExternalController.php create mode 100644 src/applications/phortune/controller/external/PhortuneExternalOverviewController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9e01eb700d..ec3ef2231b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5294,6 +5294,8 @@ phutil_register_library_map(array( 'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php', 'PhortuneDisplayException' => 'applications/phortune/exception/PhortuneDisplayException.php', 'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php', + 'PhortuneExternalController' => 'applications/phortune/controller/external/PhortuneExternalController.php', + 'PhortuneExternalOverviewController' => 'applications/phortune/controller/external/PhortuneExternalOverviewController.php', 'PhortuneInvoiceView' => 'applications/phortune/view/PhortuneInvoiceView.php', 'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php', 'PhortuneMemberHasAccountEdgeType' => 'applications/phortune/edge/PhortuneMemberHasAccountEdgeType.php', @@ -11879,6 +11881,8 @@ phutil_register_library_map(array( 'PhortuneDAO' => 'PhabricatorLiskDAO', 'PhortuneDisplayException' => 'Exception', 'PhortuneErrCode' => 'PhortuneConstants', + 'PhortuneExternalController' => 'PhortuneController', + 'PhortuneExternalOverviewController' => 'PhortuneExternalController', 'PhortuneInvoiceView' => 'AphrontTagView', 'PhortuneLandingController' => 'PhortuneController', 'PhortuneMemberHasAccountEdgeType' => 'PhabricatorEdgeType', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 18a5d45f69..43594c9653 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -104,6 +104,9 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { '(?P\d+)/(?P[^/]+)/' => 'PhortuneProviderActionController', ), + 'external/(?P[^/]+)/(?P[^/]+)/' => array( + '' => 'PhortuneExternalOverviewController', + ), 'merchant/' => array( $this->getQueryRoutePattern() => 'PhortuneMerchantListController', diff --git a/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php b/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php index 14c2b842f9..97d90946f5 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php @@ -67,6 +67,12 @@ final class PhortuneAccountEmailViewController $account->getID(), $address->getID())); + if ($can_edit) { + $external_uri = $address->getExternalURI(); + } else { + $external_uri = null; + } + $curtain = $this->newCurtainView($account); $curtain->addAction( @@ -77,6 +83,14 @@ final class PhortuneAccountEmailViewController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Show External View')) + ->setIcon('fa-eye') + ->setHref($external_uri) + ->setDisabled(!$can_edit) + ->setOpenInNewWindow(true)); + return $curtain; } diff --git a/src/applications/phortune/controller/external/PhortuneExternalController.php b/src/applications/phortune/controller/external/PhortuneExternalController.php new file mode 100644 index 0000000000..72ea02a955 --- /dev/null +++ b/src/applications/phortune/controller/external/PhortuneExternalController.php @@ -0,0 +1,147 @@ +email; + } + + final protected function getAccountEmail() { + return $this->email; + } + + final protected function getExternalViewer() { + return PhabricatorUser::getOmnipotentUser(); + } + + final public function handleRequest(AphrontRequest $request) { + $address_key = $request->getURIData('addressKey'); + $access_key = $request->getURIData('accessKey'); + + $viewer = $this->getViewer(); + $xviewer = $this->getExternalViewer(); + + $email = id(new PhortuneAccountEmailQuery()) + ->setViewer($xviewer) + ->withAddressKeys(array($address_key)) + ->executeOne(); + if (!$email) { + return new Aphront404Response(); + } + + $account = $email->getAccount(); + + $can_see = PhabricatorPolicyFilter::hasCapability( + $viewer, + $account, + PhabricatorPolicyCapability::CAN_EDIT); + + $email_display = phutil_tag('strong', array(), $email->getAddress()); + $user_display = phutil_tag('strong', array(), $viewer->getUsername()); + + $actual_key = $email->getAccessKey(); + if (!phutil_hashes_are_identical($access_key, $actual_key)) { + $dialog = $this->newDialog() + ->setTitle(pht('Email Access Link Out of Date')) + ->appendParagraph( + pht( + 'You are trying to access this payment account as: %s', + $email_display)) + ->appendParagraph( + pht( + 'The access link you have followed is out of date and no longer '. + 'works.')); + + if ($can_see) { + $dialog->appendParagraph( + pht( + 'You are currently logged in as a user (%s) who has '. + 'permission to manage the payment account, so you can '. + 'continue to the updated link.', + $user_display)); + + $dialog->addCancelButton( + $email->getExternalURI(), + pht('Continue to Updated Link')); + } else { + $dialog->appendParagraph( + pht( + 'To access information about this payment account, follow '. + 'a more recent link or ask a user with access to give you '. + 'an updated link.')); + } + + return $dialog; + } + + // TODO: Test that status is good. + + $this->email = $email; + + return $this->handleExternalRequest($request); + } + + final protected function newExternalCrumbs() { + $viewer = $this->getViewer(); + + $crumbs = new PHUICrumbsView(); + + if ($this->hasAccountEmail()) { + $email = $this->getAccountEmail(); + $account = $email->getAccount(); + + $crumb_name = pht( + 'Payment Account: %s', + $account->getName()); + + $crumb = id(new PHUICrumbView()) + ->setIcon('fa-diamond') + ->setName($crumb_name); + + $can_see = PhabricatorPolicyFilter::hasCapability( + $viewer, + $account, + PhabricatorPolicyCapability::CAN_VIEW); + if ($can_see) { + $crumb->setHref($account->getURI()); + } + + $crumbs + ->addCrumb($crumb) + ->addTextCrumb(pht('Viewing As "%s"', $email->getAddress())); + } else { + $crumb = id(new PHUICrumbView()) + ->setIcon('fa-diamond') + ->setText(pht('External Account View')); + + $crumbs->addCrumb($crumb); + } + + return $crumbs; + } + + final protected function newExternalView() { + $email = $this->getAccountEmail(); + + $messages = array(); + $messages[] = pht( + 'You are viewing this payment account as: %s', + phutil_tag('strong', array(), $email->getAddress())); + $messages[] = pht( + 'Anyone who has a link to this page can view order history for '. + 'this payment account.'); + + return id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors($messages); + } +} diff --git a/src/applications/phortune/controller/external/PhortuneExternalOverviewController.php b/src/applications/phortune/controller/external/PhortuneExternalOverviewController.php new file mode 100644 index 0000000000..bcd68e2f4a --- /dev/null +++ b/src/applications/phortune/controller/external/PhortuneExternalOverviewController.php @@ -0,0 +1,91 @@ +getExternalViewer(); + $email = $this->getAccountEmail(); + $account = $email->getAccount(); + + $crumbs = $this->newExternalCrumbs() + ->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Invoices and Receipts: %s', $account->getName())); + + $external_view = $this->newExternalView(); + $invoices_view = $this->newInvoicesView(); + $receipts_view = $this->newReceiptsView(); + + $column_view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter( + array( + $external_view, + $invoices_view, + $receipts_view, + )); + + return $this->newPage() + ->setCrumbs($crumbs) + ->setTitle( + array( + pht('Invoices and Receipts'), + $account->getName(), + )) + ->appendChild($column_view); + } + + private function newInvoicesView() { + $xviewer = $this->getExternalViewer(); + $email = $this->getAccountEmail(); + $account = $email->getAccount(); + + $invoices = id(new PhortuneCartQuery()) + ->setViewer($xviewer) + ->withAccountPHIDs(array($account->getPHID())) + ->needPurchases(true) + ->withInvoices(true) + ->execute(); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Invoices')); + + $invoices_table = id(new PhortuneOrderTableView()) + ->setViewer($xviewer) + ->setCarts($invoices) + ->setIsInvoices(true); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($invoices_table); + } + + private function newReceiptsView() { + $xviewer = $this->getExternalViewer(); + $email = $this->getAccountEmail(); + $account = $email->getAccount(); + + $receipts = id(new PhortuneCartQuery()) + ->setViewer($xviewer) + ->withAccountPHIDs(array($account->getPHID())) + ->needPurchases(true) + ->withInvoices(false) + ->execute(); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Receipts')); + + $receipts_table = id(new PhortuneOrderTableView()) + ->setViewer($xviewer) + ->setCarts($receipts); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($receipts_table); + } + +} diff --git a/src/applications/phortune/query/PhortuneAccountEmailQuery.php b/src/applications/phortune/query/PhortuneAccountEmailQuery.php index 4494372ef6..81684d1fdd 100644 --- a/src/applications/phortune/query/PhortuneAccountEmailQuery.php +++ b/src/applications/phortune/query/PhortuneAccountEmailQuery.php @@ -6,6 +6,7 @@ final class PhortuneAccountEmailQuery private $ids; private $phids; private $accountPHIDs; + private $addressKeys; public function withIDs(array $ids) { $this->ids = $ids; @@ -22,6 +23,11 @@ final class PhortuneAccountEmailQuery return $this; } + public function withAddressKeys(array $keys) { + $this->addressKeys = $keys; + return $this; + } + public function newResultObject() { return new PhortuneAccountEmail(); } @@ -77,6 +83,13 @@ final class PhortuneAccountEmailQuery $this->accountPHIDs); } + if ($this->addressKeys !== null) { + $where[] = qsprintf( + $conn, + 'address.addressKey IN (%Ls)', + $this->addressKeys); + } + return $where; } diff --git a/src/applications/phortune/storage/PhortuneAccountEmail.php b/src/applications/phortune/storage/PhortuneAccountEmail.php index 5f1ede8414..65c67c56d2 100644 --- a/src/applications/phortune/storage/PhortuneAccountEmail.php +++ b/src/applications/phortune/storage/PhortuneAccountEmail.php @@ -78,6 +78,13 @@ final class PhortuneAccountEmail $this->getID()); } + public function getExternalURI() { + return urisprintf( + '/phortune/external/%s/%s/', + $this->getAddressKey(), + $this->getAccessKey()); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/phortune/view/PhortuneOrderTableView.php b/src/applications/phortune/view/PhortuneOrderTableView.php index b4b8866160..28dd1e58b2 100644 --- a/src/applications/phortune/view/PhortuneOrderTableView.php +++ b/src/applications/phortune/view/PhortuneOrderTableView.php @@ -49,6 +49,7 @@ final class PhortuneOrderTableView extends AphrontView { $is_invoices = $this->getIsInvoices(); $is_merchant = $this->getIsMerchantView(); + $is_external = (!$viewer->getPHID()); $phids = array(); foreach ($carts as $cart) { @@ -69,14 +70,18 @@ final class PhortuneOrderTableView extends AphrontView { if (count($purchases) == 1) { $purchase = head($purchases); - $purchase_name = $handles[$purchase->getPHID()]->renderLink(); + $purchase_name = $handles[$purchase->getPHID()]->getName(); $purchases = array(); } else { $purchase_name = ''; } if ($is_invoices) { - $merchant_link = $handles[$cart->getMerchantPHID()]->renderLink(); + if ($is_external) { + $merchant_link = $handles[$cart->getMerchantPHID()]->getName(); + } else { + $merchant_link = $handles[$cart->getMerchantPHID()]->renderLink(); + } } else { $merchant_link = null; } @@ -97,13 +102,12 @@ final class PhortuneOrderTableView extends AphrontView { PhortuneCart::getNameForStatus($cart->getStatus()), phabricator_datetime($cart->getDateModified(), $viewer), phabricator_datetime($cart->getDateCreated(), $viewer), - phutil_tag( - 'a', - array( - 'href' => $cart->getCheckoutURI(), - 'class' => 'small button button-green', - ), - pht('Pay Now')), + id(new PHUIButtonView()) + ->setTag('a') + ->setColor('green') + ->setHref($cart->getCheckoutURI()) + ->setText(pht('Pay Now')) + ->setIcon('fa-credit-card'), ); foreach ($purchases as $purchase) { $id = $purchase->getID(); @@ -164,7 +168,7 @@ final class PhortuneOrderTableView extends AphrontView { // We show "Pay Now" for due invoices, but not if the viewer is the // merchant, since it doesn't make sense for them to pay. - ($is_invoices && !$is_merchant), + ($is_invoices && !$is_merchant && !$is_external), )); return $table; From 4e13551e8546bf4036f352499a9893f3edba0455 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 23 Aug 2019 09:32:52 -0700 Subject: [PATCH 25/27] Add credential rotation and statuses (disabled, unsubscribed) to Phortune external email Summary: Depends on D20737. Ref T13367. Allow external addresses to have their access key rotated. Account managers can disable them, and anyone with the link can permanently unsubscribe them. Test Plan: Enabled/disabled addresses; permanently unsubscribed addresses. Maniphest Tasks: T13367 Differential Revision: https://secure.phabricator.com/D20738 --- src/__phutil_library_map__.php | 10 ++ .../PhabricatorPhortuneApplication.php | 8 +- .../PhortuneAccountEmailRotateController.php | 62 ++++++++ .../PhortuneAccountEmailStatusController.php | 137 ++++++++++++++++++ .../PhortuneAccountEmailViewController.php | 68 ++++++++- .../external/PhortuneExternalController.php | 24 ++- .../PhortuneExternalOverviewController.php | 9 +- .../PhortuneExternalUnsubscribeController.php | 67 +++++++++ .../phortune/storage/PhortuneAccountEmail.php | 7 + .../PhortuneAccountEmailRotateTransaction.php | 23 +++ .../PhortuneAccountEmailStatusTransaction.php | 23 +++ 11 files changed, 434 insertions(+), 4 deletions(-) create mode 100644 src/applications/phortune/controller/account/PhortuneAccountEmailRotateController.php create mode 100644 src/applications/phortune/controller/account/PhortuneAccountEmailStatusController.php create mode 100644 src/applications/phortune/controller/external/PhortuneExternalUnsubscribeController.php create mode 100644 src/applications/phortune/xaction/PhortuneAccountEmailRotateTransaction.php create mode 100644 src/applications/phortune/xaction/PhortuneAccountEmailStatusTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ec3ef2231b..c4be7187bd 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5237,7 +5237,11 @@ phutil_register_library_map(array( 'PhortuneAccountEmailEditor' => 'applications/phortune/editor/PhortuneAccountEmailEditor.php', 'PhortuneAccountEmailPHIDType' => 'applications/phortune/phid/PhortuneAccountEmailPHIDType.php', 'PhortuneAccountEmailQuery' => 'applications/phortune/query/PhortuneAccountEmailQuery.php', + 'PhortuneAccountEmailRotateController' => 'applications/phortune/controller/account/PhortuneAccountEmailRotateController.php', + 'PhortuneAccountEmailRotateTransaction' => 'applications/phortune/xaction/PhortuneAccountEmailRotateTransaction.php', 'PhortuneAccountEmailStatus' => 'applications/phortune/constants/PhortuneAccountEmailStatus.php', + 'PhortuneAccountEmailStatusController' => 'applications/phortune/controller/account/PhortuneAccountEmailStatusController.php', + 'PhortuneAccountEmailStatusTransaction' => 'applications/phortune/xaction/PhortuneAccountEmailStatusTransaction.php', 'PhortuneAccountEmailTransaction' => 'applications/phortune/storage/PhortuneAccountEmailTransaction.php', 'PhortuneAccountEmailTransactionQuery' => 'applications/phortune/query/PhortuneAccountEmailTransactionQuery.php', 'PhortuneAccountEmailTransactionType' => 'applications/phortune/xaction/PhortuneAccountEmailTransactionType.php', @@ -5296,6 +5300,7 @@ phutil_register_library_map(array( 'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php', 'PhortuneExternalController' => 'applications/phortune/controller/external/PhortuneExternalController.php', 'PhortuneExternalOverviewController' => 'applications/phortune/controller/external/PhortuneExternalOverviewController.php', + 'PhortuneExternalUnsubscribeController' => 'applications/phortune/controller/external/PhortuneExternalUnsubscribeController.php', 'PhortuneInvoiceView' => 'applications/phortune/view/PhortuneInvoiceView.php', 'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php', 'PhortuneMemberHasAccountEdgeType' => 'applications/phortune/edge/PhortuneMemberHasAccountEdgeType.php', @@ -11815,7 +11820,11 @@ phutil_register_library_map(array( 'PhortuneAccountEmailEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneAccountEmailPHIDType' => 'PhabricatorPHIDType', 'PhortuneAccountEmailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhortuneAccountEmailRotateController' => 'PhortuneAccountController', + 'PhortuneAccountEmailRotateTransaction' => 'PhortuneAccountEmailTransactionType', 'PhortuneAccountEmailStatus' => 'Phobject', + 'PhortuneAccountEmailStatusController' => 'PhortuneAccountController', + 'PhortuneAccountEmailStatusTransaction' => 'PhortuneAccountEmailTransactionType', 'PhortuneAccountEmailTransaction' => 'PhabricatorModularTransaction', 'PhortuneAccountEmailTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortuneAccountEmailTransactionType' => 'PhabricatorModularTransactionType', @@ -11883,6 +11892,7 @@ phutil_register_library_map(array( 'PhortuneErrCode' => 'PhortuneConstants', 'PhortuneExternalController' => 'PhortuneController', 'PhortuneExternalOverviewController' => 'PhortuneExternalController', + 'PhortuneExternalUnsubscribeController' => 'PhortuneExternalController', 'PhortuneInvoiceView' => 'AphrontTagView', 'PhortuneLandingController' => 'PhortuneController', 'PhortuneMemberHasAccountEdgeType' => 'PhabricatorEdgeType', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 43594c9653..9fc1a0ad01 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -87,7 +87,12 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { ), 'addresses/' => array( '' => 'PhortuneAccountEmailAddressesController', - '(?P\d+)/' => 'PhortuneAccountEmailViewController', + '(?P\d+)/' => array( + '' => 'PhortuneAccountEmailViewController', + 'rotate/' => 'PhortuneAccountEmailRotateController', + '(?Pdisable|enable)/' + => 'PhortuneAccountEmailStatusController', + ), $this->getEditRoutePattern('edit/') => 'PhortuneAccountEmailEditController', ), @@ -106,6 +111,7 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { ), 'external/(?P[^/]+)/(?P[^/]+)/' => array( '' => 'PhortuneExternalOverviewController', + 'unsubscribe/' => 'PhortuneExternalUnsubscribeController', ), 'merchant/' => array( $this->getQueryRoutePattern() diff --git a/src/applications/phortune/controller/account/PhortuneAccountEmailRotateController.php b/src/applications/phortune/controller/account/PhortuneAccountEmailRotateController.php new file mode 100644 index 0000000000..2155ebcd20 --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountEmailRotateController.php @@ -0,0 +1,62 @@ +getViewer(); + $account = $this->getAccount(); + + $address = id(new PhortuneAccountEmailQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->withIDs(array($request->getURIData('addressID'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$address) { + return new Aphront404Response(); + } + + $address_uri = $address->getURI(); + + if ($request->isFormOrHisecPost()) { + $xactions = array(); + + $xactions[] = $address->getApplicationTransactionTemplate() + ->setTransactionType( + PhortuneAccountEmailRotateTransaction::TRANSACTIONTYPE) + ->setNewValue(true); + + $address->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) + ->setCancelURI($address_uri) + ->applyTransactions($address, $xactions); + + return id(new AphrontRedirectResponse())->setURI($address_uri); + } + + return $this->newDialog() + ->setTitle(pht('Rotate Access Key')) + ->appendParagraph( + pht( + 'Rotate the access key for email address %s?', + phutil_tag('strong', array(), $address->getAddress()))) + ->appendParagraph( + pht( + 'Existing access links which have been sent to this email address '. + 'will stop working.')) + ->addSubmitButton(pht('Rotate Access Key')) + ->addCancelButton($address_uri); + } +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountEmailStatusController.php b/src/applications/phortune/controller/account/PhortuneAccountEmailStatusController.php new file mode 100644 index 0000000000..c11564b000 --- /dev/null +++ b/src/applications/phortune/controller/account/PhortuneAccountEmailStatusController.php @@ -0,0 +1,137 @@ +getViewer(); + $account = $this->getAccount(); + + $address = id(new PhortuneAccountEmailQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->withIDs(array($request->getURIData('addressID'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$address) { + return new Aphront404Response(); + } + + $address_uri = $address->getURI(); + + $is_enable = false; + $is_disable = false; + + $old_status = $address->getStatus(); + switch ($request->getURIData('action')) { + case 'enable': + if ($old_status === PhortuneAccountEmailStatus::STATUS_ACTIVE) { + return $this->newDialog() + ->setTitle(pht('Already Enabled')) + ->appendParagraph( + pht( + 'You can not enable this address because it is already '. + 'active.')) + ->addCancelButton($address_uri); + } + + if ($old_status === PhortuneAccountEmailStatus::STATUS_UNSUBSCRIBED) { + return $this->newDialog() + ->setTitle(pht('Permanently Unsubscribed')) + ->appendParagraph( + pht( + 'You can not enable this address because it has been '. + 'permanently unsubscribed.')) + ->addCancelButton($address_uri); + } + + $new_status = PhortuneAccountEmailStatus::STATUS_ACTIVE; + $is_enable = true; + break; + case 'disable': + if ($old_status === PhortuneAccountEmailStatus::STATUS_DISABLED) { + return $this->newDialog() + ->setTitle(pht('Already Disabled')) + ->appendParagraph( + pht( + 'You can not disabled this address because it is already '. + 'disabled.')) + ->addCancelButton($address_uri); + } + + if ($old_status === PhortuneAccountEmailStatus::STATUS_UNSUBSCRIBED) { + return $this->newDialog() + ->setTitle(pht('Permanently Unsubscribed')) + ->appendParagraph( + pht( + 'You can not disable this address because it has been '. + 'permanently unsubscribed.')) + ->addCancelButton($address_uri); + } + + $new_status = PhortuneAccountEmailStatus::STATUS_DISABLED; + $is_disable = true; + break; + default: + return new Aphront404Response(); + } + + if ($request->isFormOrHisecPost()) { + $xactions = array(); + + $xactions[] = $address->getApplicationTransactionTemplate() + ->setTransactionType( + PhortuneAccountEmailStatusTransaction::TRANSACTIONTYPE) + ->setNewValue($new_status); + + $address->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) + ->setCancelURI($address_uri) + ->applyTransactions($address, $xactions); + + return id(new AphrontRedirectResponse())->setURI($address_uri); + } + + $dialog = $this->newDialog(); + + $body = array(); + + if ($is_disable) { + $title = pht('Disable Address'); + + $body[] = pht( + 'This address will no longer receive email, and access links will '. + 'no longer function.'); + + $submit = pht('Disable Address'); + } else { + $title = pht('Enable Address'); + + $body[] = pht( + 'This address will receive email again, and existing links '. + 'to access order history will work again.'); + + $submit = pht('Enable Address'); + } + + foreach ($body as $graph) { + $dialog->appendParagraph($graph); + } + + return $dialog + ->setTitle($title) + ->addCancelButton($address_uri) + ->addSubmitButton($submit); + } +} diff --git a/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php b/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php index 97d90946f5..4e15210890 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php @@ -14,7 +14,7 @@ final class PhortuneAccountEmailViewController $address = id(new PhortuneAccountEmailQuery()) ->setViewer($viewer) ->withAccountPHIDs(array($account->getPHID())) - ->withIDs(array($request->getURIData('id'))) + ->withIDs(array($request->getURIData('addressID'))) ->executeOne(); if (!$address) { return new Aphront404Response(); @@ -83,6 +83,56 @@ final class PhortuneAccountEmailViewController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); + switch ($address->getStatus()) { + case PhortuneAccountEmailStatus::STATUS_ACTIVE: + $disable_name = pht('Disable Address'); + $disable_icon = 'fa-times'; + $can_disable = true; + $disable_action = 'disable'; + break; + case PhortuneAccountEmailStatus::STATUS_DISABLED: + $disable_name = pht('Enable Address'); + $disable_icon = 'fa-check'; + $can_disable = true; + $disable_action = 'enable'; + break; + case PhortuneAccountEmailStatus::STATUS_UNSUBSCRIBED: + $disable_name = pht('Disable Address'); + $disable_icon = 'fa-times'; + $can_disable = false; + $disable_action = 'disable'; + break; + } + + $disable_uri = $this->getApplicationURI( + urisprintf( + 'account/%d/addresses/%d/%s/', + $account->getID(), + $address->getID(), + $disable_action)); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName($disable_name) + ->setIcon($disable_icon) + ->setHref($disable_uri) + ->setDisabled(!$can_disable) + ->setWorkflow(true)); + + $rotate_uri = $this->getApplicationURI( + urisprintf( + 'account/%d/addresses/%d/rotate/', + $account->getID(), + $address->getID())); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Rotate Access Key')) + ->setIcon('fa-refresh') + ->setHref($rotate_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Show External View')) @@ -100,7 +150,23 @@ final class PhortuneAccountEmailViewController $view = id(new PHUIPropertyListView()) ->setUser($viewer); + $access_key = $address->getAccessKey(); + + // This is not a meaningful security barrier: the full plaintext of the + // access key is visible on the page in the link target of the "Show + // External View" action. It's just here to make it clear "Rotate Access + // Key" actually does something. + + $prefix_length = 4; + $visible_part = substr($access_key, 0, $prefix_length); + $masked_part = str_repeat( + "\xE2\x80\xA2", + strlen($access_key) - $prefix_length); + $access_display = $visible_part.$masked_part; + $access_display = phutil_tag('tt', array(), $access_display); + $view->addProperty(pht('Email Address'), $address->getAddress()); + $view->addProperty(pht('Access Key'), $access_display); return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Email Address Details')) diff --git a/src/applications/phortune/controller/external/PhortuneExternalController.php b/src/applications/phortune/controller/external/PhortuneExternalController.php index 72ea02a955..a9ae254499 100644 --- a/src/applications/phortune/controller/external/PhortuneExternalController.php +++ b/src/applications/phortune/controller/external/PhortuneExternalController.php @@ -83,7 +83,29 @@ abstract class PhortuneExternalController return $dialog; } - // TODO: Test that status is good. + switch ($email->getStatus()) { + case PhortuneAccountEmailStatus::STATUS_ACTIVE: + break; + case PhortuneAccountEmailStatus::STATUS_DISABLED: + return $this->newDialog() + ->setTitle(pht('Address Disabled')) + ->appendParagraph( + pht( + 'This email address (%s) has been disabled and no longer has '. + 'access to this payment account.', + $email_display)); + case PhortuneAccountEmailStatus::STATUS_UNSUBSCRIBED: + return $this->newDialog() + ->setTitle(pht('Permanently Unsubscribed')) + ->appendParagraph( + pht( + 'This email address (%s) has been permanently unsubscribed '. + 'and no longer has access to this payment account.', + $email_display)); + break; + default: + return new Aphront404Response(); + } $this->email = $email; diff --git a/src/applications/phortune/controller/external/PhortuneExternalOverviewController.php b/src/applications/phortune/controller/external/PhortuneExternalOverviewController.php index bcd68e2f4a..ec360d4e60 100644 --- a/src/applications/phortune/controller/external/PhortuneExternalOverviewController.php +++ b/src/applications/phortune/controller/external/PhortuneExternalOverviewController.php @@ -12,7 +12,14 @@ final class PhortuneExternalOverviewController ->setBorder(true); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Invoices and Receipts: %s', $account->getName())); + ->setHeader(pht('Invoices and Receipts: %s', $account->getName())) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-times') + ->setText(pht('Unsubscribe')) + ->setHref($email->getUnsubscribeURI()) + ->setWorkflow(true)); $external_view = $this->newExternalView(); $invoices_view = $this->newInvoicesView(); diff --git a/src/applications/phortune/controller/external/PhortuneExternalUnsubscribeController.php b/src/applications/phortune/controller/external/PhortuneExternalUnsubscribeController.php new file mode 100644 index 0000000000..c7c29129d3 --- /dev/null +++ b/src/applications/phortune/controller/external/PhortuneExternalUnsubscribeController.php @@ -0,0 +1,67 @@ +getExternalViewer(); + $email = $this->getAccountEmail(); + $account = $email->getAccount(); + + $email_uri = $email->getExternalURI(); + + if ($request->isFormOrHisecPost()) { + $xactions = array(); + + $xactions[] = $email->getApplicationTransactionTemplate() + ->setTransactionType( + PhortuneAccountEmailStatusTransaction::TRANSACTIONTYPE) + ->setNewValue(PhortuneAccountEmailStatus::STATUS_UNSUBSCRIBED); + + $email->getApplicationTransactionEditor() + ->setActor($xviewer) + ->setActingAsPHID($email->getPHID()) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) + ->setCancelURI($email_uri) + ->applyTransactions($email, $xactions); + + return id(new AphrontRedirectResponse())->setURI($email_uri); + } + + $email_display = phutil_tag( + 'strong', + array(), + $email->getAddress()); + + $account_display = phutil_tag( + 'strong', + array(), + $account->getName()); + + $submit = pht( + 'Permanently Unsubscribe (%s)', + $email->getAddress()); + + return $this->newDialog() + ->setTitle(pht('Permanently Unsubscribe')) + ->appendParagraph( + pht( + 'Permanently unsubscribe this email address (%s) from this '. + 'payment account (%s)?', + $email_display, + $account_display)) + ->appendParagraph( + pht( + 'You will no longer receive email and access links will no longer '. + 'function.')) + ->appendParagraph( + pht( + 'This action is permanent and can not be undone.')) + ->addCancelButton($email_uri) + ->addSubmitButton($submit); + + } + +} diff --git a/src/applications/phortune/storage/PhortuneAccountEmail.php b/src/applications/phortune/storage/PhortuneAccountEmail.php index 65c67c56d2..c33f92928c 100644 --- a/src/applications/phortune/storage/PhortuneAccountEmail.php +++ b/src/applications/phortune/storage/PhortuneAccountEmail.php @@ -85,6 +85,13 @@ final class PhortuneAccountEmail $this->getAccessKey()); } + public function getUnsubscribeURI() { + return urisprintf( + '/phortune/external/%s/%s/unsubscribe/', + $this->getAddressKey(), + $this->getAccessKey()); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/phortune/xaction/PhortuneAccountEmailRotateTransaction.php b/src/applications/phortune/xaction/PhortuneAccountEmailRotateTransaction.php new file mode 100644 index 0000000000..e885b2d67b --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneAccountEmailRotateTransaction.php @@ -0,0 +1,23 @@ +setAccessKey($access_key); + } + + public function getTitle() { + return pht( + '%s rotated the access key for this email address.', + $this->renderAuthor()); + } + +} diff --git a/src/applications/phortune/xaction/PhortuneAccountEmailStatusTransaction.php b/src/applications/phortune/xaction/PhortuneAccountEmailStatusTransaction.php new file mode 100644 index 0000000000..e607db2f99 --- /dev/null +++ b/src/applications/phortune/xaction/PhortuneAccountEmailStatusTransaction.php @@ -0,0 +1,23 @@ +getStatus(); + } + + public function applyInternalEffects($object, $value) { + $object->setStatus($value); + } + + public function getTitle() { + return pht( + '%s changed the status for this address to %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } + +} From a0a38797127f091db7cdc3c782fc693206937183 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 23 Aug 2019 13:07:45 -0700 Subject: [PATCH 26/27] In Phortune, send order email to account external addresses Summary: Depends on D20738. Ref T13366. Fixes T8389. Now that the infrastructure is in place, actually send email to external addresses. Test Plan: Used `bin/phortune invoice` to generate invoices and saw associated external accounts receive mail in `bin/mail list-outbound`. Maniphest Tasks: T13366, T8389 Differential Revision: https://secure.phabricator.com/D20739 --- src/__phutil_library_map__.php | 2 + .../PhabricatorPhortuneApplication.php | 3 + .../external/PhortuneExternalController.php | 25 ++--- .../PhortuneExternalOrderController.php | 40 ++++++++ .../PhortuneExternalOverviewController.php | 3 + .../phortune/editor/PhortuneCartEditor.php | 97 +++++++++++++++++++ .../query/PhortuneAccountEmailQuery.php | 13 +++ .../phortune/storage/PhortuneAccountEmail.php | 8 ++ .../phortune/storage/PhortuneCart.php | 4 + .../phortune/view/PhortuneOrderTableView.php | 25 ++++- .../worker/PhortuneSubscriptionWorker.php | 1 + ...habricatorApplicationTransactionEditor.php | 8 ++ 12 files changed, 216 insertions(+), 13 deletions(-) create mode 100644 src/applications/phortune/controller/external/PhortuneExternalOrderController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c4be7187bd..97de6148f6 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5299,6 +5299,7 @@ phutil_register_library_map(array( 'PhortuneDisplayException' => 'applications/phortune/exception/PhortuneDisplayException.php', 'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php', 'PhortuneExternalController' => 'applications/phortune/controller/external/PhortuneExternalController.php', + 'PhortuneExternalOrderController' => 'applications/phortune/controller/external/PhortuneExternalOrderController.php', 'PhortuneExternalOverviewController' => 'applications/phortune/controller/external/PhortuneExternalOverviewController.php', 'PhortuneExternalUnsubscribeController' => 'applications/phortune/controller/external/PhortuneExternalUnsubscribeController.php', 'PhortuneInvoiceView' => 'applications/phortune/view/PhortuneInvoiceView.php', @@ -11891,6 +11892,7 @@ phutil_register_library_map(array( 'PhortuneDisplayException' => 'Exception', 'PhortuneErrCode' => 'PhortuneConstants', 'PhortuneExternalController' => 'PhortuneController', + 'PhortuneExternalOrderController' => 'PhortuneExternalController', 'PhortuneExternalOverviewController' => 'PhortuneExternalController', 'PhortuneExternalUnsubscribeController' => 'PhortuneExternalController', 'PhortuneInvoiceView' => 'AphrontTagView', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 9fc1a0ad01..fbc168eb58 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -112,6 +112,9 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { 'external/(?P[^/]+)/(?P[^/]+)/' => array( '' => 'PhortuneExternalOverviewController', 'unsubscribe/' => 'PhortuneExternalUnsubscribeController', + 'order/' => array( + '(?P[^/]+)/' => 'PhortuneExternalOrderController', + ), ), 'merchant/' => array( $this->getQueryRoutePattern() diff --git a/src/applications/phortune/controller/external/PhortuneExternalController.php b/src/applications/phortune/controller/external/PhortuneExternalController.php index a9ae254499..2810142531 100644 --- a/src/applications/phortune/controller/external/PhortuneExternalController.php +++ b/src/applications/phortune/controller/external/PhortuneExternalController.php @@ -127,19 +127,11 @@ abstract class PhortuneExternalController $crumb = id(new PHUICrumbView()) ->setIcon('fa-diamond') - ->setName($crumb_name); - - $can_see = PhabricatorPolicyFilter::hasCapability( - $viewer, - $account, - PhabricatorPolicyCapability::CAN_VIEW); - if ($can_see) { - $crumb->setHref($account->getURI()); - } + ->setName($crumb_name) + ->setHref($email->getExternalURI()); $crumbs - ->addCrumb($crumb) - ->addTextCrumb(pht('Viewing As "%s"', $email->getAddress())); + ->addCrumb($crumb); } else { $crumb = id(new PHUICrumbView()) ->setIcon('fa-diamond') @@ -153,11 +145,22 @@ abstract class PhortuneExternalController final protected function newExternalView() { $email = $this->getAccountEmail(); + $xviewer = $this->getExternalViewer(); + + $origin_phid = $email->getAuthorPHID(); + + $handles = $xviewer->loadHandles(array($origin_phid)); + $messages = array(); $messages[] = pht( 'You are viewing this payment account as: %s', phutil_tag('strong', array(), $email->getAddress())); + + $messages[] = pht( + 'This email address was added to this payment account by: %s', + phutil_tag('strong', array(), $handles[$origin_phid]->getFullName())); + $messages[] = pht( 'Anyone who has a link to this page can view order history for '. 'this payment account.'); diff --git a/src/applications/phortune/controller/external/PhortuneExternalOrderController.php b/src/applications/phortune/controller/external/PhortuneExternalOrderController.php new file mode 100644 index 0000000000..82b231d344 --- /dev/null +++ b/src/applications/phortune/controller/external/PhortuneExternalOrderController.php @@ -0,0 +1,40 @@ +getExternalViewer(); + $email = $this->getAccountEmail(); + $account = $email->getAccount(); + + $order = id(new PhortuneCartQuery()) + ->setViewer($xviewer) + ->withAccountPHIDs(array($account->getPHID())) + ->withIDs(array($request->getURIData('orderID'))) + ->executeOne(); + if (!$order) { + return new Aphront404Response(); + } + + $timeline = $this->buildTransactionTimeline( + $order, + new PhortuneCartTransactionQuery()); + $timeline->setShouldTerminate(true); + + $crumbs = $this->newExternalCrumbs() + ->addTextCrumb($order->getObjectName()); + + $view = id(new PHUITwoColumnView()) + ->setMainColumn( + array( + $timeline, + )); + + return $this->newPage() + ->setTitle(pht('Order %d', $order->getID())) + ->setCrumbs($crumbs) + ->appendChild($view); + } + +} diff --git a/src/applications/phortune/controller/external/PhortuneExternalOverviewController.php b/src/applications/phortune/controller/external/PhortuneExternalOverviewController.php index ec360d4e60..5db74ac75e 100644 --- a/src/applications/phortune/controller/external/PhortuneExternalOverviewController.php +++ b/src/applications/phortune/controller/external/PhortuneExternalOverviewController.php @@ -9,6 +9,7 @@ final class PhortuneExternalOverviewController $account = $email->getAccount(); $crumbs = $this->newExternalCrumbs() + ->addTextCrumb(pht('Viewing As "%s"', $email->getAddress())) ->setBorder(true); $header = id(new PHUIHeaderView()) @@ -61,6 +62,7 @@ final class PhortuneExternalOverviewController $invoices_table = id(new PhortuneOrderTableView()) ->setViewer($xviewer) + ->setAccountEmail($email) ->setCarts($invoices) ->setIsInvoices(true); @@ -87,6 +89,7 @@ final class PhortuneExternalOverviewController $receipts_table = id(new PhortuneOrderTableView()) ->setViewer($xviewer) + ->setAccountEmail($email) ->setCarts($receipts); return id(new PHUIObjectBoxView()) diff --git a/src/applications/phortune/editor/PhortuneCartEditor.php b/src/applications/phortune/editor/PhortuneCartEditor.php index f95ea2450a..aef59f059a 100644 --- a/src/applications/phortune/editor/PhortuneCartEditor.php +++ b/src/applications/phortune/editor/PhortuneCartEditor.php @@ -246,5 +246,102 @@ final class PhortuneCartEditor return $xactions; } + protected function newAuxiliaryMail($object, array $xactions) { + $xviewer = PhabricatorUser::getOmnipotentUser(); + $account = $object->getAccount(); + + $addresses = id(new PhortuneAccountEmailQuery()) + ->setViewer($xviewer) + ->withAccountPHIDs(array($account->getPHID())) + ->withStatuses( + array( + PhortuneAccountEmailStatus::STATUS_ACTIVE, + )) + ->execute(); + + $messages = array(); + foreach ($addresses as $address) { + $message = $this->newExternalMail($address, $object, $xactions); + if ($message) { + $messages[] = $message; + } + } + + return $messages; + } + + private function newExternalMail( + PhortuneAccountEmail $email, + PhortuneCart $cart, + array $xactions) { + $xviewer = PhabricatorUser::getOmnipotentUser(); + $account = $cart->getAccount(); + + $id = $cart->getID(); + $name = $cart->getName(); + + $origin_user = id(new PhabricatorPeopleQuery()) + ->setViewer($xviewer) + ->withPHIDs(array($email->getAuthorPHID())) + ->executeOne(); + if (!$origin_user) { + return null; + } + + if ($this->isInvoice()) { + $subject = pht('[Invoice #%d] %s', $id, $name); + $order_header = pht('INVOICE DETAIL'); + } else { + $subject = pht('[Order #%d] %s', $id, $name); + $order_header = pht('ORDER DETAIL'); + } + + $body = id(new PhabricatorMetaMTAMailBody()) + ->setViewer($xviewer) + ->setContextObject($cart); + + $origin_username = $origin_user->getUsername(); + $origin_realname = $origin_user->getRealName(); + if (strlen($origin_realname)) { + $origin_display = pht('%s (%s)', $origin_username, $origin_realname); + } else { + $origin_display = pht('%s', $origin_username); + } + + $body->addRawSection( + pht( + 'This email address (%s) was added to a payment account (%s) '. + 'by %s.', + $email->getAddress(), + $account->getName(), + $origin_display)); + + $body->addLinkSection( + $order_header, + PhabricatorEnv::getProductionURI($email->getExternalOrderURI($cart))); + + $body->addLinkSection( + pht('FULL ORDER HISTORY'), + PhabricatorEnv::getProductionURI($email->getExternalURI())); + + $body->addLinkSection( + pht('UNSUBSCRIBE'), + PhabricatorEnv::getProductionURI($email->getUnsubscribeURI())); + + return id(new PhabricatorMetaMTAMail()) + ->setFrom($this->getActingAsPHID()) + ->setSubject($subject) + ->addRawTos( + array( + $email->getAddress(), + )) + ->setForceDelivery(true) + ->setIsBulk(true) + ->setSensitiveContent(true) + ->setBody($body->render()) + ->setHTMLBody($body->renderHTML()); + + } + } diff --git a/src/applications/phortune/query/PhortuneAccountEmailQuery.php b/src/applications/phortune/query/PhortuneAccountEmailQuery.php index 81684d1fdd..0bdfdb78dc 100644 --- a/src/applications/phortune/query/PhortuneAccountEmailQuery.php +++ b/src/applications/phortune/query/PhortuneAccountEmailQuery.php @@ -7,6 +7,7 @@ final class PhortuneAccountEmailQuery private $phids; private $accountPHIDs; private $addressKeys; + private $statuses; public function withIDs(array $ids) { $this->ids = $ids; @@ -28,6 +29,11 @@ final class PhortuneAccountEmailQuery return $this; } + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + public function newResultObject() { return new PhortuneAccountEmail(); } @@ -90,6 +96,13 @@ final class PhortuneAccountEmailQuery $this->addressKeys); } + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn, + 'address.status IN (%Ls)', + $this->statuses); + } + return $where; } diff --git a/src/applications/phortune/storage/PhortuneAccountEmail.php b/src/applications/phortune/storage/PhortuneAccountEmail.php index c33f92928c..e6761500cb 100644 --- a/src/applications/phortune/storage/PhortuneAccountEmail.php +++ b/src/applications/phortune/storage/PhortuneAccountEmail.php @@ -92,6 +92,14 @@ final class PhortuneAccountEmail $this->getAccessKey()); } + public function getExternalOrderURI(PhortuneCart $cart) { + return urisprintf( + '/phortune/external/%s/%s/order/%d/', + $this->getAddressKey(), + $this->getAccessKey(), + $cart->getID()); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/phortune/storage/PhortuneCart.php b/src/applications/phortune/storage/PhortuneCart.php index 624a13d6b0..91171037c6 100644 --- a/src/applications/phortune/storage/PhortuneCart.php +++ b/src/applications/phortune/storage/PhortuneCart.php @@ -656,6 +656,10 @@ final class PhortuneCart extends PhortuneDAO return idx($this->metadata, $key, $default); } + public function getObjectName() { + return pht('Order %d', $this->getID()); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/phortune/view/PhortuneOrderTableView.php b/src/applications/phortune/view/PhortuneOrderTableView.php index 28dd1e58b2..d7868f8d49 100644 --- a/src/applications/phortune/view/PhortuneOrderTableView.php +++ b/src/applications/phortune/view/PhortuneOrderTableView.php @@ -6,6 +6,7 @@ final class PhortuneOrderTableView extends AphrontView { private $noDataString; private $isInvoices; private $isMerchantView; + private $accountEmail; public function setCarts(array $carts) { $this->carts = $carts; @@ -43,13 +44,24 @@ final class PhortuneOrderTableView extends AphrontView { return $this->isMerchantView; } + public function setAccountEmail(PhortuneAccountEmail $account_email) { + $this->accountEmail = $account_email; + return $this; + } + + public function getAccountEmail() { + return $this->accountEmail; + } + public function render() { $carts = $this->getCarts(); $viewer = $this->getUser(); $is_invoices = $this->getIsInvoices(); $is_merchant = $this->getIsMerchantView(); - $is_external = (!$viewer->getPHID()); + $is_external = (bool)$this->getAccountEmail(); + + $email = $this->getAccountEmail(); $phids = array(); foreach ($carts as $cart) { @@ -65,7 +77,16 @@ final class PhortuneOrderTableView extends AphrontView { $rows = array(); $rowc = array(); foreach ($carts as $cart) { - $cart_link = $handles[$cart->getPHID()]->renderLink(); + if ($is_external) { + $cart_link = phutil_tag( + 'a', + array( + 'href' => $email->getExternalOrderURI($cart), + ), + $handles[$cart->getPHID()]->getName()); + } else { + $cart_link = $handles[$cart->getPHID()]->renderLink(); + } $purchases = $cart->getPurchases(); if (count($purchases) == 1) { diff --git a/src/applications/phortune/worker/PhortuneSubscriptionWorker.php b/src/applications/phortune/worker/PhortuneSubscriptionWorker.php index 1c9a3f0fa0..438ebf5b1b 100644 --- a/src/applications/phortune/worker/PhortuneSubscriptionWorker.php +++ b/src/applications/phortune/worker/PhortuneSubscriptionWorker.php @@ -22,6 +22,7 @@ final class PhortuneSubscriptionWorker extends PhabricatorWorker { return; } + $account = $subscription->getAccount(); $merchant = $subscription->getMerchant(); diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 01294e308a..c17ac1ec72 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -1520,6 +1520,10 @@ abstract class PhabricatorApplicationTransactionEditor } } + foreach ($this->newAuxiliaryMail($object, $xactions) as $message) { + $messages[] = $message; + } + // NOTE: This actually sends the mail. We do this last to reduce the chance // that we send some mail, hit an exception, then send the mail again when // retrying. @@ -4799,6 +4803,10 @@ abstract class PhabricatorApplicationTransactionEditor return $extensions; } + protected function newAuxiliaryMail($object, array $xactions) { + return array(); + } + private function generateMailStamps($object, $data) { if (!$data || !is_array($data)) { return null; From 97a4a59cf2c7f3fcf8cf013655cab4b4185a99b8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 24 Aug 2019 09:43:27 -0700 Subject: [PATCH 27/27] Give the Phortune external portal an order view Summary: Depends on D20739. Ref T13366. Slightly modularize/update components of order views, and make orders viewable from either an account context (existing view) or an external context (new view). The new view is generally simpler so this mostly just reorganizes existing code. Test Plan: Viewed orders as an account owner and an external user. Maniphest Tasks: T13366 Differential Revision: https://secure.phabricator.com/D20740 --- src/__phutil_library_map__.php | 10 +- .../base/controller/PhabricatorController.php | 13 +- .../PhabricatorPhortuneApplication.php | 5 +- .../cart/PhortuneCartCheckoutController.php | 15 +- .../cart/PhortuneCartController.php | 57 --- .../cart/PhortuneCartViewController.php | 287 +++----------- .../PhortuneExternalOrderController.php | 88 ++++- .../phortune/storage/PhortuneAccountEmail.php | 8 + .../phortune/view/PhortuneInvoiceView.php | 159 -------- .../view/PhortuneOrderDescriptionView.php | 39 ++ .../phortune/view/PhortuneOrderItemsView.php | 58 +++ .../view/PhortuneOrderSummaryView.php | 370 ++++++++++++++++++ .../phortune/view/PhortuneOrderView.php | 17 + 13 files changed, 663 insertions(+), 463 deletions(-) delete mode 100644 src/applications/phortune/view/PhortuneInvoiceView.php create mode 100644 src/applications/phortune/view/PhortuneOrderDescriptionView.php create mode 100644 src/applications/phortune/view/PhortuneOrderItemsView.php create mode 100644 src/applications/phortune/view/PhortuneOrderSummaryView.php create mode 100644 src/applications/phortune/view/PhortuneOrderView.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 97de6148f6..81ac776d5d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5302,7 +5302,6 @@ phutil_register_library_map(array( 'PhortuneExternalOrderController' => 'applications/phortune/controller/external/PhortuneExternalOrderController.php', 'PhortuneExternalOverviewController' => 'applications/phortune/controller/external/PhortuneExternalOverviewController.php', 'PhortuneExternalUnsubscribeController' => 'applications/phortune/controller/external/PhortuneExternalUnsubscribeController.php', - 'PhortuneInvoiceView' => 'applications/phortune/view/PhortuneInvoiceView.php', 'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php', 'PhortuneMemberHasAccountEdgeType' => 'applications/phortune/edge/PhortuneMemberHasAccountEdgeType.php', 'PhortuneMemberHasMerchantEdgeType' => 'applications/phortune/edge/PhortuneMemberHasMerchantEdgeType.php', @@ -5343,7 +5342,11 @@ phutil_register_library_map(array( 'PhortuneMerchantTransactionQuery' => 'applications/phortune/query/PhortuneMerchantTransactionQuery.php', 'PhortuneMerchantTransactionType' => 'applications/phortune/xaction/PhortuneMerchantTransactionType.php', 'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php', + 'PhortuneOrderDescriptionView' => 'applications/phortune/view/PhortuneOrderDescriptionView.php', + 'PhortuneOrderItemsView' => 'applications/phortune/view/PhortuneOrderItemsView.php', + 'PhortuneOrderSummaryView' => 'applications/phortune/view/PhortuneOrderSummaryView.php', 'PhortuneOrderTableView' => 'applications/phortune/view/PhortuneOrderTableView.php', + 'PhortuneOrderView' => 'applications/phortune/view/PhortuneOrderView.php', 'PhortunePayPalPaymentProvider' => 'applications/phortune/provider/PhortunePayPalPaymentProvider.php', 'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php', 'PhortunePaymentMethodCreateController' => 'applications/phortune/controller/paymentmethod/PhortunePaymentMethodCreateController.php', @@ -11895,7 +11898,6 @@ phutil_register_library_map(array( 'PhortuneExternalOrderController' => 'PhortuneExternalController', 'PhortuneExternalOverviewController' => 'PhortuneExternalController', 'PhortuneExternalUnsubscribeController' => 'PhortuneExternalController', - 'PhortuneInvoiceView' => 'AphrontTagView', 'PhortuneLandingController' => 'PhortuneController', 'PhortuneMemberHasAccountEdgeType' => 'PhabricatorEdgeType', 'PhortuneMemberHasMerchantEdgeType' => 'PhabricatorEdgeType', @@ -11940,7 +11942,11 @@ phutil_register_library_map(array( 'PhortuneMerchantTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortuneMerchantTransactionType' => 'PhabricatorModularTransactionType', 'PhortuneMonthYearExpiryControl' => 'AphrontFormControl', + 'PhortuneOrderDescriptionView' => 'AphrontView', + 'PhortuneOrderItemsView' => 'PhortuneOrderView', + 'PhortuneOrderSummaryView' => 'PhortuneOrderView', 'PhortuneOrderTableView' => 'AphrontView', + 'PhortuneOrderView' => 'AphrontView', 'PhortunePayPalPaymentProvider' => 'PhortunePaymentProvider', 'PhortunePaymentMethod' => array( 'PhortuneDAO', diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index cfd0eaee65..a463c741d1 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -481,7 +481,7 @@ abstract class PhabricatorController extends AphrontController { protected function buildTransactionTimeline( PhabricatorApplicationTransactionInterface $object, - PhabricatorApplicationTransactionQuery $query, + PhabricatorApplicationTransactionQuery $query = null, PhabricatorMarkupEngine $engine = null, $view_data = array()) { @@ -489,6 +489,17 @@ abstract class PhabricatorController extends AphrontController { $viewer = $this->getViewer(); $xaction = $object->getApplicationTransactionTemplate(); + if (!$query) { + $query = PhabricatorApplicationTransactionQuery::newQueryForObject( + $object); + if (!$query) { + throw new Exception( + pht( + 'Unable to find transaction query for object of class "%s".', + get_class($object))); + } + } + $pager = id(new AphrontCursorPagerView()) ->readFromRequest($request) ->setURI(new PhutilURI( diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index fbc168eb58..79b246770e 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -113,7 +113,10 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { '' => 'PhortuneExternalOverviewController', 'unsubscribe/' => 'PhortuneExternalUnsubscribeController', 'order/' => array( - '(?P[^/]+)/' => 'PhortuneExternalOrderController', + '(?P[^/]+)/' => array( + '' => 'PhortuneExternalOrderController', + '(?Pprint)/' => 'PhortuneExternalOrderController', + ), ), ), 'merchant/' => array( diff --git a/src/applications/phortune/controller/cart/PhortuneCartCheckoutController.php b/src/applications/phortune/controller/cart/PhortuneCartCheckoutController.php index 4b59afef75..793187c404 100644 --- a/src/applications/phortune/controller/cart/PhortuneCartCheckoutController.php +++ b/src/applications/phortune/controller/cart/PhortuneCartCheckoutController.php @@ -207,7 +207,9 @@ final class PhortuneCartCheckoutController ->appendChild($form) ->appendChild($provider_form); - $description_box = $this->renderCartDescription($cart); + $description_view = id(new PhortuneOrderDescriptionView()) + ->setViewer($viewer) + ->setOrder($cart); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Checkout')); @@ -220,11 +222,12 @@ final class PhortuneCartCheckoutController $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFooter(array( - $cart_box, - $description_box, - $payment_box, - )); + ->setFooter( + array( + $description_view, + $cart_box, + $payment_box, + )); return $this->newPage() ->setTitle($title) diff --git a/src/applications/phortune/controller/cart/PhortuneCartController.php b/src/applications/phortune/controller/cart/PhortuneCartController.php index 2f5f55014e..c28581bc47 100644 --- a/src/applications/phortune/controller/cart/PhortuneCartController.php +++ b/src/applications/phortune/controller/cart/PhortuneCartController.php @@ -74,61 +74,4 @@ abstract class PhortuneCartController PhabricatorPolicyCapability::CAN_EDIT); } - protected function buildCartContentTable(PhortuneCart $cart) { - - $rows = array(); - foreach ($cart->getPurchases() as $purchase) { - $rows[] = array( - $purchase->getFullDisplayName(), - $purchase->getBasePriceAsCurrency()->formatForDisplay(), - $purchase->getQuantity(), - $purchase->getTotalPriceAsCurrency()->formatForDisplay(), - ); - } - - $rows[] = array( - phutil_tag('strong', array(), pht('Total')), - '', - '', - phutil_tag('strong', array(), - $cart->getTotalPriceAsCurrency()->formatForDisplay()), - ); - - $table = new AphrontTableView($rows); - $table->setHeaders( - array( - pht('Item'), - pht('Price'), - pht('Qty.'), - pht('Total'), - )); - $table->setColumnClasses( - array( - 'wide', - 'right', - 'right', - 'right', - )); - - return $table; - } - - protected function renderCartDescription(PhortuneCart $cart) { - $description = $cart->getDescription(); - if (!strlen($description)) { - return null; - } - - $output = new PHUIRemarkupView($this->getViewer(), $description); - - $box = id(new PHUIBoxView()) - ->addMargin(PHUI::MARGIN_LARGE) - ->appendChild($output); - - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Description')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($box); - } - } diff --git a/src/applications/phortune/controller/cart/PhortuneCartViewController.php b/src/applications/phortune/controller/cart/PhortuneCartViewController.php index d728a202c9..9412d11045 100644 --- a/src/applications/phortune/controller/cart/PhortuneCartViewController.php +++ b/src/applications/phortune/controller/cart/PhortuneCartViewController.php @@ -3,8 +3,6 @@ final class PhortuneCartViewController extends PhortuneCartController { - private $action = null; - protected function shouldRequireAccountAuthority() { return false; } @@ -15,214 +13,93 @@ final class PhortuneCartViewController protected function handleCartRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $cart = $this->getCart(); + $order = $this->getCart(); $authority = $this->getMerchantAuthority(); $can_edit = $this->hasAccountAuthority(); - $this->action = $request->getURIData('action'); + $is_printable = ($request->getURIData('action') === 'print'); - $cart_table = $this->buildCartContentTable($cart); - - $errors = array(); - $error_view = null; $resume_uri = null; - switch ($cart->getStatus()) { - case PhortuneCart::STATUS_READY: - if ($cart->getIsInvoice()) { - $error_view = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->appendChild(pht('This invoice is ready for payment.')); - } - break; - case PhortuneCart::STATUS_PURCHASING: - if ($can_edit) { - $resume_uri = $cart->getMetadataValue('provider.checkoutURI'); - if ($resume_uri) { - $errors[] = pht( - 'The checkout process has been started, but not yet completed. '. - 'You can continue checking out by clicking %s, or cancel the '. - 'order, or contact the merchant for assistance.', - phutil_tag('strong', array(), pht('Continue Checkout'))); - } else { - $errors[] = pht( - 'The checkout process has been started, but an error occurred. '. - 'You can cancel the order or contact the merchant for '. - 'assistance.'); - } - } - break; - case PhortuneCart::STATUS_CHARGED: - if ($can_edit) { - $errors[] = pht( - 'You have been charged, but processing could not be completed. '. - 'You can cancel your order, or contact the merchant for '. - 'assistance.'); - } - break; - case PhortuneCart::STATUS_HOLD: - if ($can_edit) { - $errors[] = pht( - 'Payment for this order is on hold. You can click %s to check '. - 'for updates, cancel the order, or contact the merchant for '. - 'assistance.', - phutil_tag('strong', array(), pht('Update Status'))); - } - break; - case PhortuneCart::STATUS_REVIEW: - if ($authority) { - $errors[] = pht( - 'This order has been flagged for manual review. Review the order '. - 'and choose %s to accept it or %s to reject it.', - phutil_tag('strong', array(), pht('Accept Order')), - phutil_tag('strong', array(), pht('Refund Order'))); - } else if ($can_edit) { - $errors[] = pht( - 'This order requires manual processing and will complete once '. - 'the merchant accepts it.'); - } - break; - case PhortuneCart::STATUS_PURCHASED: - $error_view = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_SUCCESS) - ->appendChild(pht('This purchase has been completed.')); - break; + if ($order->getStatus() === PhortuneCart::STATUS_PURCHASING) { + if ($can_edit) { + $resume_uri = $order->getMetadataValue('provider.checkoutURI'); + } } - if ($errors) { - $error_view = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING) - ->appendChild($errors); - } - - $details = $this->buildDetailsView($cart); - $curtain = $this->buildCurtainView( - $cart, - $can_edit, - $authority, - $resume_uri); - $header = id(new PHUIHeaderView()) ->setUser($viewer) - ->setHeader($cart->getName()) + ->setHeader($order->getName()) ->setHeaderIcon('fa-shopping-bag'); - if ($cart->getStatus() == PhortuneCart::STATUS_PURCHASED) { - $done_uri = $cart->getDoneURI(); + if ($order->getStatus() == PhortuneCart::STATUS_PURCHASED) { + $done_uri = $order->getDoneURI(); if ($done_uri) { $header->addActionLink( id(new PHUIButtonView()) ->setTag('a') ->setHref($done_uri) ->setIcon('fa-check-square green') - ->setText($cart->getDoneActionName())); + ->setText($order->getDoneActionName())); } } - $cart_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Cart Items')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($cart_table); - - $description = $this->renderCartDescription($cart); - - $charges = id(new PhortuneChargeQuery()) + $order_view = id(new PhortuneOrderSummaryView()) ->setViewer($viewer) - ->withCartPHIDs(array($cart->getPHID())) - ->needCarts(true) - ->execute(); + ->setOrder($order) + ->setResumeURI($resume_uri) + ->setPrintable($is_printable); - $charges_table = id(new PhortuneChargeTableView()) - ->setUser($viewer) - ->setCharges($charges) - ->setShowOrder(false); + $crumbs = null; + $curtain = null; - $charges = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Charges')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($charges_table); + $main = array(); + $tail = array(); - $account = $cart->getAccount(); + require_celerity_resource('phortune-invoice-css'); - $crumbs = $this->buildApplicationCrumbs() - ->addTextCrumb($account->getName(), $account->getURI()) - ->addTextCrumb(pht('Orders'), $account->getOrdersURI()) - ->addTextCrumb(pht('Cart %d', $cart->getID())) - ->setBorder(true); - - require_celerity_resource('phortune-css'); - - if (!$this->action) { - $class = 'phortune-cart-page'; - $timeline = $this->buildTransactionTimeline( - $cart, - new PhortuneCartTransactionQuery()); - $timeline - ->setShouldTerminate(true); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setCurtain($curtain) - ->setMainColumn(array( - $error_view, - $details, - $cart_box, - $description, - $charges, - $timeline, - )); + if ($is_printable) { + $body_class = 'phortune-invoice-view'; + $tail[] = $order_view; } else { - $class = 'phortune-invoice-view'; - $crumbs = null; - $merchant_phid = $cart->getMerchantPHID(); - $buyer_phid = $cart->getAuthorPHID(); - $merchant = id(new PhortuneMerchantQuery()) - ->setViewer($viewer) - ->withPHIDs(array($merchant_phid)) - ->needProfileImage(true) - ->executeOne(); - $buyer = id(new PhabricatorPeopleQuery()) - ->setViewer($viewer) - ->withPHIDs(array($buyer_phid)) - ->needProfileImage(true) - ->executeOne(); + $body_class = 'phortune-cart-page'; - $merchant_contact = new PHUIRemarkupView( - $viewer, - $merchant->getContactInfo()); + $curtain = $this->buildCurtainView( + $order, + $can_edit, + $authority, + $resume_uri); - $account_name = $account->getBillingName(); - if (!strlen($account_name)) { - $account_name = $buyer->getRealName(); - } + $account = $order->getAccount(); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($account->getName(), $account->getURI()) + ->addTextCrumb(pht('Orders'), $account->getOrdersURI()) + ->addTextCrumb($order->getObjectName()) + ->setBorder(true); - $account_contact = $account->getBillingAddress(); - if (strlen($account_contact)) { - $account_contact = new PHUIRemarkupView( - $viewer, - $account_contact); - } + $timeline = $this->buildTransactionTimeline($order) + ->setShouldTerminate(true); - $view = id(new PhortuneInvoiceView()) - ->setMerchantName($merchant->getName()) - ->setMerchantLogo($merchant->getProfileImageURI()) - ->setMerchantContact($merchant_contact) - ->setMerchantFooter($merchant->getInvoiceFooter()) - ->setAccountName($account_name) - ->setAccountContact($account_contact) - ->setStatus($error_view) - ->setContent( - array( - $details, - $cart_box, - $charges, - )); + $main[] = $order_view; + $main[] = $timeline; + } + + $column_view = id(new PHUITwoColumnView()) + ->setMainColumn($main) + ->setFooter($tail); + + if ($curtain) { + $column_view->setCurtain($curtain); } $page = $this->newPage() - ->setTitle(pht('Cart %d', $cart->getID())) - ->addClass($class) - ->appendChild($view); + ->addClass($body_class) + ->setTitle( + array( + $order->getObjectName(), + $order->getName(), + )) + ->appendChild($column_view); if ($crumbs) { $page->setCrumbs($crumbs); @@ -231,45 +108,6 @@ final class PhortuneCartViewController return $page; } - private function buildDetailsView(PhortuneCart $cart) { - $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($cart); - - $handles = $this->loadViewerHandles( - array( - $cart->getAccountPHID(), - $cart->getAuthorPHID(), - $cart->getMerchantPHID(), - )); - - if ($this->action == 'print') { - $view->addProperty(pht('Order Name'), $cart->getName()); - } - - $view->addProperty( - pht('Account'), - $handles[$cart->getAccountPHID()]->renderLink()); - $view->addProperty( - pht('Authorized By'), - $handles[$cart->getAuthorPHID()]->renderLink()); - $view->addProperty( - pht('Merchant'), - $handles[$cart->getMerchantPHID()]->renderLink()); - $view->addProperty( - pht('Status'), - PhortuneCart::getNameForStatus($cart->getStatus())); - $view->addProperty( - pht('Updated'), - phabricator_datetime($cart->getDateModified(), $viewer)); - - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Details')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($view); - } - private function buildCurtainView( PhortuneCart $cart, $can_edit, @@ -297,6 +135,18 @@ final class PhortuneCartViewController $checkout_uri = $cart->getCheckoutURI(); $void_uri = $this->getApplicationURI("cart/{$id}/void/"); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Printable Version')) + ->setHref($print_uri) + ->setOpenInNewWindow(true) + ->setIcon('fa-print')); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Pay Now')) @@ -327,13 +177,6 @@ final class PhortuneCartViewController ->setHref($resume_uri)); } - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Printable Version')) - ->setHref($print_uri) - ->setOpenInNewWindow(true) - ->setIcon('fa-print')); - if ($authority) { $curtain->addAction( id(new PhabricatorActionView()) diff --git a/src/applications/phortune/controller/external/PhortuneExternalOrderController.php b/src/applications/phortune/controller/external/PhortuneExternalOrderController.php index 82b231d344..36522f95af 100644 --- a/src/applications/phortune/controller/external/PhortuneExternalOrderController.php +++ b/src/applications/phortune/controller/external/PhortuneExternalOrderController.php @@ -17,24 +17,82 @@ final class PhortuneExternalOrderController return new Aphront404Response(); } - $timeline = $this->buildTransactionTimeline( - $order, - new PhortuneCartTransactionQuery()); - $timeline->setShouldTerminate(true); + $is_printable = ($request->getURIData('action') === 'print'); - $crumbs = $this->newExternalCrumbs() - ->addTextCrumb($order->getObjectName()); + $order_view = id(new PhortuneOrderSummaryView()) + ->setViewer($xviewer) + ->setOrder($order) + ->setPrintable($is_printable); - $view = id(new PHUITwoColumnView()) - ->setMainColumn( + $crumbs = null; + $curtain = null; + + $main = array(); + $tail = array(); + + require_celerity_resource('phortune-invoice-css'); + + if ($is_printable) { + $body_class = 'phortune-invoice-view'; + + $tail[] = $order_view; + } else { + $body_class = 'phortune-cart-page'; + + $curtain = $this->newCurtain($order); + + $crumbs = $this->newExternalCrumbs() + ->addTextCrumb($order->getObjectName()) + ->setBorder(true); + + $timeline = $this->buildTransactionTimeline($order) + ->setShouldTerminate(true); + + $main[] = $order_view; + $main[] = $timeline; + } + + $column_view = id(new PHUITwoColumnView()) + ->setMainColumn($main) + ->setFooter($tail); + + if ($curtain) { + $column_view->setCurtain($curtain); + } + + $page = $this->newPage() + ->addClass($body_class) + ->setTitle( array( - $timeline, - )); + $order->getObjectName(), + $order->getName(), + )) + ->appendChild($column_view); - return $this->newPage() - ->setTitle(pht('Order %d', $order->getID())) - ->setCrumbs($crumbs) - ->appendChild($view); - } + if ($crumbs) { + $page->setCrumbs($crumbs); + } + + return $page; + } + + + private function newCurtain(PhortuneCart $order) { + $xviewer = $this->getExternalViewer(); + $email = $this->getAccountEmail(); + + $curtain = $this->newCurtainView($order); + + $print_uri = $email->getExternalOrderPrintURI($order); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Printable Version')) + ->setHref($print_uri) + ->setOpenInNewWindow(true) + ->setIcon('fa-print')); + + return $curtain; + } } diff --git a/src/applications/phortune/storage/PhortuneAccountEmail.php b/src/applications/phortune/storage/PhortuneAccountEmail.php index e6761500cb..50c8e90025 100644 --- a/src/applications/phortune/storage/PhortuneAccountEmail.php +++ b/src/applications/phortune/storage/PhortuneAccountEmail.php @@ -100,6 +100,14 @@ final class PhortuneAccountEmail $cart->getID()); } + public function getExternalOrderPrintURI(PhortuneCart $cart) { + return urisprintf( + '/phortune/external/%s/%s/order/%d/print/', + $this->getAddressKey(), + $this->getAccessKey(), + $cart->getID()); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/phortune/view/PhortuneInvoiceView.php b/src/applications/phortune/view/PhortuneInvoiceView.php deleted file mode 100644 index 65da418cc2..0000000000 --- a/src/applications/phortune/view/PhortuneInvoiceView.php +++ /dev/null @@ -1,159 +0,0 @@ -merchantName = $name; - return $this; - } - - public function setMerchantLogo($logo) { - $this->merchantLogo = $logo; - return $this; - } - - public function setMerchantContact($contact) { - $this->merchantContact = $contact; - return $this; - } - - public function setMerchantFooter($footer) { - $this->merchantFooter = $footer; - return $this; - } - - public function setAccountName($name) { - $this->accountName = $name; - return $this; - } - - public function setAccountContact($contact) { - $this->accountContact = $contact; - return $this; - } - - public function setStatus($status) { - $this->status = $status; - return $this; - } - - public function setContent($content) { - $this->content = $content; - return $this; - } - - protected function getTagAttributes() { - $classes = array(); - $classes[] = 'phortune-invoice-view'; - - return array( - 'class' => implode(' ', $classes), - ); - } - - protected function getTagContent() { - require_celerity_resource('phortune-invoice-css'); - - $logo = phutil_tag( - 'div', - array( - 'class' => 'phortune-invoice-logo', - ), - phutil_tag( - 'img', - array( - 'height' => '50', - 'width' => '50', - 'alt' => $this->merchantName, - 'src' => $this->merchantLogo, - ))); - - $to_title = phutil_tag( - 'div', - array( - 'class' => 'phortune-mini-header', - ), - pht('Bill To:')); - - $bill_to = phutil_tag( - 'td', - array( - 'class' => 'phortune-invoice-to', - 'width' => '50%', - ), - array( - $to_title, - phutil_tag('strong', array(), $this->accountName), - phutil_tag('br', array()), - $this->accountContact, - )); - - $from_title = phutil_tag( - 'div', - array( - 'class' => 'phortune-mini-header', - ), - pht('From:')); - - $bill_from = phutil_tag( - 'td', - array( - 'class' => 'phortune-invoice-from', - 'width' => '50%', - ), - array( - $from_title, - phutil_tag('strong', array(), $this->merchantName), - phutil_tag('br', array()), - $this->merchantContact, - )); - - $contact = phutil_tag( - 'table', - array( - 'class' => 'phortune-invoice-contact', - 'width' => '100%', - ), - phutil_tag( - 'tr', - array(), - array( - $bill_to, - $bill_from, - ))); - - $status = null; - if ($this->status) { - $status = phutil_tag( - 'div', - array( - 'class' => 'phortune-invoice-status', - ), - $this->status); - } - - $footer = phutil_tag( - 'div', - array( - 'class' => 'phortune-invoice-footer', - ), - $this->merchantFooter); - - return array( - $logo, - $contact, - $status, - $this->content, - $footer, - ); - } -} diff --git a/src/applications/phortune/view/PhortuneOrderDescriptionView.php b/src/applications/phortune/view/PhortuneOrderDescriptionView.php new file mode 100644 index 0000000000..1eaf290341 --- /dev/null +++ b/src/applications/phortune/view/PhortuneOrderDescriptionView.php @@ -0,0 +1,39 @@ +order = $order; + return $this; + } + + public function getOrder() { + return $this->order; + } + + public function render() { + $viewer = $this->getViewer(); + $order = $this->getOrder(); + + $description = $order->getDescription(); + if (!strlen($description)) { + return null; + } + + $output = new PHUIRemarkupView($viewer, $description); + + $description_box = id(new PHUIBoxView()) + ->addMargin(PHUI::MARGIN_LARGE) + ->appendChild($output); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Description')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($description_box); + } + + +} diff --git a/src/applications/phortune/view/PhortuneOrderItemsView.php b/src/applications/phortune/view/PhortuneOrderItemsView.php new file mode 100644 index 0000000000..8c7f06db13 --- /dev/null +++ b/src/applications/phortune/view/PhortuneOrderItemsView.php @@ -0,0 +1,58 @@ +getViewer(); + $order = $this->getOrder(); + + $purchases = id(new PhortunePurchaseQuery()) + ->setViewer($viewer) + ->withCartPHIDs(array($order->getPHID())) + ->execute(); + + $order->attachPurchases($purchases); + + $rows = array(); + foreach ($purchases as $purchase) { + $rows[] = array( + $purchase->getFullDisplayName(), + $purchase->getBasePriceAsCurrency()->formatForDisplay(), + $purchase->getQuantity(), + $purchase->getTotalPriceAsCurrency()->formatForDisplay(), + ); + } + + $rows[] = array( + phutil_tag('strong', array(), pht('Total')), + '', + '', + phutil_tag('strong', array(), + $order->getTotalPriceAsCurrency()->formatForDisplay()), + ); + + $table = new AphrontTableView($rows); + $table->setHeaders( + array( + pht('Item'), + pht('Price'), + pht('Qty.'), + pht('Total'), + )); + $table->setColumnClasses( + array( + 'wide', + 'right', + 'right', + 'right', + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Items')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } + + +} diff --git a/src/applications/phortune/view/PhortuneOrderSummaryView.php b/src/applications/phortune/view/PhortuneOrderSummaryView.php new file mode 100644 index 0000000000..77152b9ff7 --- /dev/null +++ b/src/applications/phortune/view/PhortuneOrderSummaryView.php @@ -0,0 +1,370 @@ +resumeURI = $resume_uri; + return $this; + } + + public function getResumeURI() { + return $this->resumeURI; + } + + public function setPrintable($printable) { + $this->printable = $printable; + return $this; + } + + public function getPrintable() { + return $this->printable; + } + + public function render() { + $is_printable = $this->getPrintable(); + + $content = array(); + + if ($is_printable) { + $content[] = $this->newContactHeader(); + } + + $content[] = $this->newMessagesView(); + $content[] = $this->newDetailsView(); + $content[] = $this->newDescriptionView(); + $content[] = $this->newItemsView(); + $content[] = $this->newChargesView(); + + if ($is_printable) { + $content[] = $this->newContactFooter(); + } + + return $content; + } + + private function newMessagesView() { + $viewer = $this->getViewer(); + $order = $this->getOrder(); + + $messages = array(); + $severity = null; + + $resume_uri = $this->getResumeURI(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $order, + PhabricatorPolicyCapability::CAN_EDIT); + + $can_merchant = PhabricatorPolicyFilter::hasCapability( + $viewer, + $order->getMerchant(), + PhabricatorPolicyCapability::CAN_EDIT); + + switch ($order->getStatus()) { + case PhortuneCart::STATUS_READY: + if ($order->getIsInvoice()) { + $severity = PHUIInfoView::SEVERITY_NOTICE; + $messages[] = pht('This invoice is ready for payment.'); + } + break; + case PhortuneCart::STATUS_PURCHASING: + if ($can_edit) { + if ($resume_uri) { + $messages[] = pht( + 'The checkout process has been started, but not yet completed. '. + 'You can continue checking out by clicking %s, or cancel the '. + 'order, or contact the merchant for assistance.', + phutil_tag('strong', array(), pht('Continue Checkout'))); + } else { + $messages[] = pht( + 'The checkout process has been started, but an error occurred. '. + 'You can cancel the order or contact the merchant for '. + 'assistance.'); + } + } + break; + case PhortuneCart::STATUS_CHARGED: + if ($can_edit) { + $messages[] = pht( + 'You have been charged, but processing could not be completed. '. + 'You can cancel your order, or contact the merchant for '. + 'assistance.'); + } + break; + case PhortuneCart::STATUS_HOLD: + if ($can_edit) { + $messages[] = pht( + 'Payment for this order is on hold. You can click %s to check '. + 'for updates, cancel the order, or contact the merchant for '. + 'assistance.', + phutil_tag('strong', array(), pht('Update Status'))); + } + break; + case PhortuneCart::STATUS_REVIEW: + if ($can_merchant) { + $messages[] = pht( + 'This order has been flagged for manual review. Review the order '. + 'and choose %s to accept it or %s to reject it.', + phutil_tag('strong', array(), pht('Accept Order')), + phutil_tag('strong', array(), pht('Refund Order'))); + } else if ($can_edit) { + $messages[] = pht( + 'This order requires manual processing and will complete once '. + 'the merchant accepts it.'); + } + break; + case PhortuneCart::STATUS_PURCHASED: + $severity = PHUIInfoView::SEVERITY_SUCCESS; + $messages[] = pht('This purchase has been completed.'); + break; + } + + if (!$messages) { + return null; + } + + if ($severity === null) { + $severity = PHUIInfoView::SEVERITY_WARNING; + } + + $messages_view = id(new PHUIInfoView()) + ->setSeverity($severity) + ->appendChild($messages); + + $is_printable = $this->getPrintable(); + if ($is_printable) { + $messages_view = phutil_tag( + 'div', + array( + 'class' => 'phortune-invoice-status', + ), + $messages_view); + } + + return $messages_view; + } + + private function newDetailsView() { + $viewer = $this->getViewer(); + $order = $this->getOrder(); + $is_printable = $this->getPrintable(); + + $view = id(new PHUIPropertyListView()) + ->setViewer($viewer) + ->setObject($order); + + $account_phid = $order->getAccountPHID(); + $author_phid = $order->getAuthorPHID(); + $merchant_phid = $order->getMerchantPHID(); + + $handles = $viewer->loadHandles( + array( + $account_phid, + $author_phid, + $merchant_phid, + )); + + if ($is_printable) { + $account_link = $handles[$account_phid]->getFullName(); + $author_link = $handles[$author_phid]->getFullName(); + $merchant_link = $handles[$merchant_phid]->getFullName(); + } else { + $account_link = $handles[$account_phid]->renderLink(); + $author_link = $handles[$author_phid]->renderLink(); + $merchant_link = $handles[$merchant_phid]->renderLink(); + } + + if ($is_printable) { + $view->addProperty(pht('Order Name'), $order->getName()); + } + + $view->addProperty(pht('Account'), $account_link); + $view->addProperty(pht('Authorized By'), $author_link); + $view->addProperty(pht('Merchant'), $merchant_link); + + $view->addProperty( + pht('Order Status'), + PhortuneCart::getNameForStatus($order->getStatus())); + $view->addProperty( + pht('Created'), + phabricator_datetime($order->getDateCreated(), $viewer)); + $view->addProperty( + pht('Updated'), + phabricator_datetime($order->getDateModified(), $viewer)); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); + } + + private function newChargesView() { + $viewer = $this->getViewer(); + $order = $this->getOrder(); + + $charges = id(new PhortuneChargeQuery()) + ->setViewer($viewer) + ->withCartPHIDs(array($order->getPHID())) + ->needCarts(true) + ->execute(); + + $charges_table = id(new PhortuneChargeTableView()) + ->setUser($viewer) + ->setCharges($charges) + ->setShowOrder(false); + + $charges_view = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Charges')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($charges_table); + + return $charges_view; + } + + private function newDescriptionView() { + $viewer = $this->getViewer(); + $order = $this->getOrder(); + + return id(new PhortuneOrderDescriptionView()) + ->setViewer($viewer) + ->setOrder($order); + } + + private function newItemsView() { + $viewer = $this->getViewer(); + $order = $this->getOrder(); + + return id(new PhortuneOrderItemsView()) + ->setViewer($viewer) + ->setOrder($order); + } + + private function newContactHeader() { + $viewer = $this->getViewer(); + $order = $this->getOrder(); + + $merchant = id(new PhortuneMerchantQuery()) + ->setViewer($viewer) + ->withPHIDs(array($order->getMerchant()->getPHID())) + ->needProfileImage(true) + ->executeOne(); + + $merchant_name = $merchant->getName(); + $merchant_image = $merchant->getProfileImageURI(); + + $account = $order->getAccount(); + $account_name = $account->getBillingName(); + + $account_contact = $account->getBillingAddress(); + if (strlen($account_contact)) { + $account_contact = new PHUIRemarkupView( + $viewer, + $account_contact); + } + + $merchant_contact = $merchant->getContactInfo(); + if (strlen($merchant_contact)) { + $merchant_contact = new PHUIRemarkupView( + $viewer, + $merchant->getContactInfo()); + } + + $logo = phutil_tag( + 'div', + array( + 'class' => 'phortune-invoice-logo', + ), + phutil_tag( + 'img', + array( + 'height' => '50', + 'width' => '50', + 'alt' => $merchant_name, + 'src' => $merchant_image, + ))); + + $to_title = phutil_tag( + 'div', + array( + 'class' => 'phortune-mini-header', + ), + pht('Bill To:')); + + $bill_to = phutil_tag( + 'td', + array( + 'class' => 'phortune-invoice-to', + 'width' => '50%', + ), + array( + $to_title, + phutil_tag('strong', array(), $account_name), + phutil_tag('br', array()), + $account_contact, + )); + + $from_title = phutil_tag( + 'div', + array( + 'class' => 'phortune-mini-header', + ), + pht('From:')); + + $bill_from = phutil_tag( + 'td', + array( + 'class' => 'phortune-invoice-from', + 'width' => '50%', + ), + array( + $from_title, + phutil_tag('strong', array(), $merchant_name), + phutil_tag('br', array()), + $merchant_contact, + )); + + $contact = phutil_tag( + 'table', + array( + 'class' => 'phortune-invoice-contact', + 'width' => '100%', + ), + phutil_tag( + 'tr', + array(), + array( + $bill_to, + $bill_from, + ))); + + return array( + $logo, + $contact, + ); + } + + private function newContactFooter() { + $viewer = $this->getViewer(); + $order = $this->getOrder(); + + $merchant = $order->getMerchant(); + $footer = $merchant->getInvoiceFooter(); + + if (!strlen($footer)) { + return null; + } + + return phutil_tag( + 'div', + array( + 'class' => 'phortune-invoice-footer', + ), + $footer); + } + +} diff --git a/src/applications/phortune/view/PhortuneOrderView.php b/src/applications/phortune/view/PhortuneOrderView.php new file mode 100644 index 0000000000..25261aa423 --- /dev/null +++ b/src/applications/phortune/view/PhortuneOrderView.php @@ -0,0 +1,17 @@ +order = $order; + return $this; + } + + final public function getOrder() { + return $this->order; + } + +}