From 704afea281b1c3127c182968778efdb04832bef3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 21 Jul 2016 10:39:59 -0700 Subject: [PATCH] Add PackagesPackage Summary: Ref T8116. A package has: - a publisher (like "Phacility"), from the previous revision; - a name (like "Arcanist"); - a package key (like "arcanist"). The package key is immutable, like the publisher key. This gives a package a full key like "phacility/arcanist". Policy stuff: - You must be able to view a publisher to view a package (currently, everyone can always see all publishers). - You must be able to edit a publisher to create a new package inside it. - Packages have separate view/edit permissions. This still does nothing interesting. Test Plan: {F1731663} Reviewers: chad Reviewed By: chad Subscribers: eadler Maniphest Tasks: T8116 Differential Revision: https://secure.phabricator.com/D16315 --- .../sql/autopatches/20160721.pack.04.pkg.sql | 13 + .../20160721.pack.05.pkgxaction.sql | 19 ++ src/__phutil_library_map__.php | 50 +++- .../PhabricatorPackagesApplication.php | 9 + ...torPackagesPackageEditConduitAPIMethod.php | 19 ++ ...rPackagesPackageSearchConduitAPIMethod.php | 18 ++ .../PhabricatorPackagesPackageController.php | 4 + ...abricatorPackagesPackageEditController.php | 12 + ...abricatorPackagesPackageListController.php | 26 ++ ...abricatorPackagesPackageViewController.php | 87 +++++++ .../PhabricatorPackagesPackageEditEngine.php | 104 ++++++++ .../PhabricatorPackagesPackageEditor.php | 55 +++++ ...PhabricatorPackagesPublisherEditEngine.php | 14 +- .../PhabricatorPackagesPackagePHIDType.php | 45 ++++ .../query/PhabricatorPackagesPackageQuery.php | 152 ++++++++++++ ...PhabricatorPackagesPackageSearchEngine.php | 77 ++++++ ...ricatorPackagesPackageTransactionQuery.php | 10 + .../PhabricatorPackagesPublisherQuery.php | 6 +- .../query/PhabricatorPackagesQuery.php | 10 + .../storage/PhabricatorPackagesPackage.php | 222 ++++++++++++++++++ .../PhabricatorPackagesPackageTransaction.php | 18 ++ .../storage/PhabricatorPackagesPublisher.php | 16 +- ...PhabricatorPackagesPublisherDatasource.php | 35 +++ ...abricatorPackagesPackageKeyTransaction.php | 47 ++++ ...bricatorPackagesPackageNameTransaction.php | 54 +++++ ...torPackagesPackagePublisherTransaction.php | 66 ++++++ ...bricatorPackagesPackageTransactionType.php | 4 + 27 files changed, 1178 insertions(+), 14 deletions(-) create mode 100644 resources/sql/autopatches/20160721.pack.04.pkg.sql create mode 100644 resources/sql/autopatches/20160721.pack.05.pkgxaction.sql create mode 100644 src/applications/packages/conduit/PhabricatorPackagesPackageEditConduitAPIMethod.php create mode 100644 src/applications/packages/conduit/PhabricatorPackagesPackageSearchConduitAPIMethod.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesPackageController.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesPackageEditController.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesPackageListController.php create mode 100644 src/applications/packages/controller/PhabricatorPackagesPackageViewController.php create mode 100644 src/applications/packages/editor/PhabricatorPackagesPackageEditEngine.php create mode 100644 src/applications/packages/editor/PhabricatorPackagesPackageEditor.php create mode 100644 src/applications/packages/phid/PhabricatorPackagesPackagePHIDType.php create mode 100644 src/applications/packages/query/PhabricatorPackagesPackageQuery.php create mode 100644 src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php create mode 100644 src/applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php create mode 100644 src/applications/packages/query/PhabricatorPackagesQuery.php create mode 100644 src/applications/packages/storage/PhabricatorPackagesPackage.php create mode 100644 src/applications/packages/storage/PhabricatorPackagesPackageTransaction.php create mode 100644 src/applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php create mode 100644 src/applications/packages/xaction/package/PhabricatorPackagesPackageKeyTransaction.php create mode 100644 src/applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php create mode 100644 src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php create mode 100644 src/applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php diff --git a/resources/sql/autopatches/20160721.pack.04.pkg.sql b/resources/sql/autopatches/20160721.pack.04.pkg.sql new file mode 100644 index 0000000000..c5f427f1c0 --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.04.pkg.sql @@ -0,0 +1,13 @@ +CREATE TABLE {$NAMESPACE}_packages.packages_package ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + name VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}, + publisherPHID VARBINARY(64) NOT NULL, + packageKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_SORT}, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + UNIQUE KEY `key_package` (publisherPHID, packageKey) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160721.pack.05.pkgxaction.sql b/resources/sql/autopatches/20160721.pack.05.pkgxaction.sql new file mode 100644 index 0000000000..7fd82569de --- /dev/null +++ b/resources/sql/autopatches/20160721.pack.05.pkgxaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_packages.packages_packagetransaction ( + 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) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fed5a1c75f..750c465efe 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2977,8 +2977,27 @@ phutil_register_library_map(array( 'PhabricatorPackagesDAO' => 'applications/packages/storage/PhabricatorPackagesDAO.php', 'PhabricatorPackagesEditEngine' => 'applications/packages/editor/PhabricatorPackagesEditEngine.php', 'PhabricatorPackagesEditor' => 'applications/packages/editor/PhabricatorPackagesEditor.php', + 'PhabricatorPackagesPackage' => 'applications/packages/storage/PhabricatorPackagesPackage.php', + 'PhabricatorPackagesPackageController' => 'applications/packages/controller/PhabricatorPackagesPackageController.php', + 'PhabricatorPackagesPackageEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPackageEditConduitAPIMethod.php', + 'PhabricatorPackagesPackageEditController' => 'applications/packages/controller/PhabricatorPackagesPackageEditController.php', + 'PhabricatorPackagesPackageEditEngine' => 'applications/packages/editor/PhabricatorPackagesPackageEditEngine.php', + 'PhabricatorPackagesPackageEditor' => 'applications/packages/editor/PhabricatorPackagesPackageEditor.php', + 'PhabricatorPackagesPackageKeyTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackageKeyTransaction.php', + 'PhabricatorPackagesPackageListController' => 'applications/packages/controller/PhabricatorPackagesPackageListController.php', + 'PhabricatorPackagesPackageNameTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php', + 'PhabricatorPackagesPackagePHIDType' => 'applications/packages/phid/PhabricatorPackagesPackagePHIDType.php', + 'PhabricatorPackagesPackagePublisherTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php', + 'PhabricatorPackagesPackageQuery' => 'applications/packages/query/PhabricatorPackagesPackageQuery.php', + 'PhabricatorPackagesPackageSearchConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPackageSearchConduitAPIMethod.php', + 'PhabricatorPackagesPackageSearchEngine' => 'applications/packages/query/PhabricatorPackagesPackageSearchEngine.php', + 'PhabricatorPackagesPackageTransaction' => 'applications/packages/storage/PhabricatorPackagesPackageTransaction.php', + 'PhabricatorPackagesPackageTransactionQuery' => 'applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php', + 'PhabricatorPackagesPackageTransactionType' => 'applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php', + 'PhabricatorPackagesPackageViewController' => 'applications/packages/controller/PhabricatorPackagesPackageViewController.php', 'PhabricatorPackagesPublisher' => 'applications/packages/storage/PhabricatorPackagesPublisher.php', 'PhabricatorPackagesPublisherController' => 'applications/packages/controller/PhabricatorPackagesPublisherController.php', + 'PhabricatorPackagesPublisherDatasource' => 'applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php', 'PhabricatorPackagesPublisherEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPublisherEditConduitAPIMethod.php', 'PhabricatorPackagesPublisherEditController' => 'applications/packages/controller/PhabricatorPackagesPublisherEditController.php', 'PhabricatorPackagesPublisherEditEngine' => 'applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php', @@ -2994,6 +3013,7 @@ phutil_register_library_map(array( 'PhabricatorPackagesPublisherTransactionQuery' => 'applications/packages/query/PhabricatorPackagesPublisherTransactionQuery.php', 'PhabricatorPackagesPublisherTransactionType' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherTransactionType.php', 'PhabricatorPackagesPublisherViewController' => 'applications/packages/controller/PhabricatorPackagesPublisherViewController.php', + 'PhabricatorPackagesQuery' => 'applications/packages/query/PhabricatorPackagesQuery.php', 'PhabricatorPackagesSchemaSpec' => 'applications/packages/storage/PhabricatorPackagesSchemaSpec.php', 'PhabricatorPackagesTransactionType' => 'applications/packages/xaction/PhabricatorPackagesTransactionType.php', 'PhabricatorPagerUIExample' => 'applications/uiexample/examples/PhabricatorPagerUIExample.php', @@ -7754,6 +7774,32 @@ phutil_register_library_map(array( 'PhabricatorPackagesDAO' => 'PhabricatorLiskDAO', 'PhabricatorPackagesEditEngine' => 'PhabricatorEditEngine', 'PhabricatorPackagesEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorPackagesPackage' => array( + 'PhabricatorPackagesDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', + 'PhabricatorDestructibleInterface', + 'PhabricatorSubscribableInterface', + 'PhabricatorProjectInterface', + 'PhabricatorConduitResultInterface', + ), + 'PhabricatorPackagesPackageController' => 'PhabricatorPackagesController', + 'PhabricatorPackagesPackageEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', + 'PhabricatorPackagesPackageEditController' => 'PhabricatorPackagesPackageController', + 'PhabricatorPackagesPackageEditEngine' => 'PhabricatorPackagesEditEngine', + 'PhabricatorPackagesPackageEditor' => 'PhabricatorPackagesEditor', + 'PhabricatorPackagesPackageKeyTransaction' => 'PhabricatorPackagesPackageTransactionType', + 'PhabricatorPackagesPackageListController' => 'PhabricatorPackagesPackageController', + 'PhabricatorPackagesPackageNameTransaction' => 'PhabricatorPackagesPackageTransactionType', + 'PhabricatorPackagesPackagePHIDType' => 'PhabricatorPHIDType', + 'PhabricatorPackagesPackagePublisherTransaction' => 'PhabricatorPackagesPackageTransactionType', + 'PhabricatorPackagesPackageQuery' => 'PhabricatorPackagesQuery', + 'PhabricatorPackagesPackageSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'PhabricatorPackagesPackageSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorPackagesPackageTransaction' => 'PhabricatorModularTransaction', + 'PhabricatorPackagesPackageTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorPackagesPackageTransactionType' => 'PhabricatorPackagesTransactionType', + 'PhabricatorPackagesPackageViewController' => 'PhabricatorPackagesPackageController', 'PhabricatorPackagesPublisher' => array( 'PhabricatorPackagesDAO', 'PhabricatorPolicyInterface', @@ -7764,6 +7810,7 @@ phutil_register_library_map(array( 'PhabricatorConduitResultInterface', ), 'PhabricatorPackagesPublisherController' => 'PhabricatorPackagesController', + 'PhabricatorPackagesPublisherDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorPackagesPublisherEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhabricatorPackagesPublisherEditController' => 'PhabricatorPackagesPublisherController', 'PhabricatorPackagesPublisherEditEngine' => 'PhabricatorPackagesEditEngine', @@ -7772,13 +7819,14 @@ phutil_register_library_map(array( 'PhabricatorPackagesPublisherListController' => 'PhabricatorPackagesPublisherController', 'PhabricatorPackagesPublisherNameTransaction' => 'PhabricatorPackagesPublisherTransactionType', 'PhabricatorPackagesPublisherPHIDType' => 'PhabricatorPHIDType', - 'PhabricatorPackagesPublisherQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorPackagesPublisherQuery' => 'PhabricatorPackagesQuery', 'PhabricatorPackagesPublisherSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhabricatorPackagesPublisherSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorPackagesPublisherTransaction' => 'PhabricatorModularTransaction', 'PhabricatorPackagesPublisherTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorPackagesPublisherTransactionType' => 'PhabricatorPackagesTransactionType', 'PhabricatorPackagesPublisherViewController' => 'PhabricatorPackagesPublisherController', + 'PhabricatorPackagesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPackagesSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorPackagesTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorPagerUIExample' => 'PhabricatorUIExample', diff --git a/src/applications/packages/application/PhabricatorPackagesApplication.php b/src/applications/packages/application/PhabricatorPackagesApplication.php index 0f6b47cbf9..818dc31bf0 100644 --- a/src/applications/packages/application/PhabricatorPackagesApplication.php +++ b/src/applications/packages/application/PhabricatorPackagesApplication.php @@ -31,6 +31,9 @@ final class PhabricatorPackagesApplication extends PhabricatorApplication { '/package/' => array( '(?P[^/]+)/' => array( '' => 'PhabricatorPackagesPublisherViewController', + '(?P[^/]+)/' => array( + '' => 'PhabricatorPackagesPackageViewController', + ), ), ), '/packages/' => array( @@ -40,6 +43,12 @@ final class PhabricatorPackagesApplication extends PhabricatorApplication { $this->getEditRoutePattern('edit/') => 'PhabricatorPackagesPublisherEditController', ), + 'package/' => array( + $this->getQueryRoutePattern() => + 'PhabricatorPackagesPackageListController', + $this->getEditRoutePattern('edit/') => + 'PhabricatorPackagesPackageEditController', + ), ), ); } diff --git a/src/applications/packages/conduit/PhabricatorPackagesPackageEditConduitAPIMethod.php b/src/applications/packages/conduit/PhabricatorPackagesPackageEditConduitAPIMethod.php new file mode 100644 index 0000000000..53b9709d36 --- /dev/null +++ b/src/applications/packages/conduit/PhabricatorPackagesPackageEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesPackageListController.php b/src/applications/packages/controller/PhabricatorPackagesPackageListController.php new file mode 100644 index 0000000000..5b29ab83f6 --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesPackageListController.php @@ -0,0 +1,26 @@ +setController($this) + ->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + id(new PhabricatorPackagesPackageEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); + + return $crumbs; + } + +} diff --git a/src/applications/packages/controller/PhabricatorPackagesPackageViewController.php b/src/applications/packages/controller/PhabricatorPackagesPackageViewController.php new file mode 100644 index 0000000000..f0a9483a28 --- /dev/null +++ b/src/applications/packages/controller/PhabricatorPackagesPackageViewController.php @@ -0,0 +1,87 @@ +getViewer(); + + $publisher_key = $request->getURIData('publisherKey'); + $package_key = $request->getURIData('packageKey'); + $full_key = $publisher_key.'/'.$package_key; + + $package = id(new PhabricatorPackagesPackageQuery()) + ->setViewer($viewer) + ->withFullKeys(array($full_key)) + ->executeOne(); + if (!$package) { + return new Aphront404Response(); + } + + $publisher = $package->getPublisher(); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($publisher->getName(), $publisher->getURI()) + ->addTextCrumb($package->getName()) + ->setBorder(true); + + $header = $this->buildHeaderView($package); + $curtain = $this->buildCurtain($package); + + $timeline = $this->buildTransactionTimeline( + $package, + new PhabricatorPackagesPackageTransactionQuery()); + + $package_view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn($timeline); + + return $this->newPage() + ->setCrumbs($crumbs) + ->setPageObjectPHIDs( + array( + $package->getPHID(), + )) + ->appendChild($package_view); + } + + + private function buildHeaderView(PhabricatorPackagesPackage $package) { + $viewer = $this->getViewer(); + $name = $package->getName(); + + return id(new PHUIHeaderView()) + ->setViewer($viewer) + ->setHeader($name) + ->setPolicyObject($package) + ->setHeaderIcon('fa-gift'); + } + + private function buildCurtain(PhabricatorPackagesPackage $package) { + $viewer = $this->getViewer(); + $curtain = $this->newCurtainView($package); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $package, + PhabricatorPolicyCapability::CAN_EDIT); + + $id = $package->getID(); + $edit_uri = $this->getApplicationURI("package/edit/{$id}/"); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Package')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setHref($edit_uri)); + + return $curtain; + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesPackageEditEngine.php b/src/applications/packages/editor/PhabricatorPackagesPackageEditEngine.php new file mode 100644 index 0000000000..5d151e6bf4 --- /dev/null +++ b/src/applications/packages/editor/PhabricatorPackagesPackageEditEngine.php @@ -0,0 +1,104 @@ +getViewer(); + return PhabricatorPackagesPackage::initializeNewPackage($viewer); + } + + protected function newObjectQuery() { + return new PhabricatorPackagesPackageQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Package'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Package'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Package: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Package'); + } + + protected function getObjectCreateShortText() { + return pht('Create Package'); + } + + protected function getObjectName() { + return pht('Package'); + } + + protected function getEditorURI() { + return '/packages/package/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/packages/package/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + $fields = array(); + + if ($this->getIsCreate()) { + $fields[] = id(new PhabricatorDatasourceEditField()) + ->setKey('publisher') + ->setAliases(array('publisherPHID')) + ->setLabel(pht('Publisher')) + ->setDescription(pht('Publisher for this package.')) + ->setTransactionType( + PhabricatorPackagesPackagePublisherTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setDatasource(new PhabricatorPackagesPublisherDatasource()) + ->setSingleValue($object->getPublisherPHID()); + } + + $fields[] = id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the package.')) + ->setTransactionType( + PhabricatorPackagesPackageNameTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getName()); + + if ($this->getIsCreate()) { + $fields[] = id(new PhabricatorTextEditField()) + ->setKey('packageKey') + ->setLabel(pht('Package Key')) + ->setDescription(pht('Unique key to identify the package.')) + ->setTransactionType( + PhabricatorPackagesPackageKeyTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getPackageKey()); + } + + return $fields; + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesPackageEditor.php b/src/applications/packages/editor/PhabricatorPackagesPackageEditor.php new file mode 100644 index 0000000000..1446fa3cac --- /dev/null +++ b/src/applications/packages/editor/PhabricatorPackagesPackageEditor.php @@ -0,0 +1,55 @@ +getPackageKey()), + null); + + throw new PhabricatorApplicationTransactionValidationException($errors); + } + +} diff --git a/src/applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php b/src/applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php index e4f027a49e..521aec7ee0 100644 --- a/src/applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php +++ b/src/applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php @@ -76,13 +76,13 @@ final class PhabricatorPackagesPublisherEditEngine if ($this->getIsCreate()) { $fields[] = id(new PhabricatorTextEditField()) - ->setKey('publisherKey') - ->setLabel(pht('Publisher Key')) - ->setDescription(pht('Unique key to identify the publisher.')) - ->setTransactionType( - PhabricatorPackagesPublisherKeyTransaction::TRANSACTIONTYPE) - ->setIsRequired(true) - ->setValue($object->getPublisherKey()); + ->setKey('publisherKey') + ->setLabel(pht('Publisher Key')) + ->setDescription(pht('Unique key to identify the publisher.')) + ->setTransactionType( + PhabricatorPackagesPublisherKeyTransaction::TRANSACTIONTYPE) + ->setIsRequired(true) + ->setValue($object->getPublisherKey()); } return $fields; diff --git a/src/applications/packages/phid/PhabricatorPackagesPackagePHIDType.php b/src/applications/packages/phid/PhabricatorPackagesPackagePHIDType.php new file mode 100644 index 0000000000..6f8e982c81 --- /dev/null +++ b/src/applications/packages/phid/PhabricatorPackagesPackagePHIDType.php @@ -0,0 +1,45 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $package = $objects[$phid]; + + $name = $package->getName(); + $uri = $package->getURI(); + + $handle + ->setName($name) + ->setURI($uri); + } + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPackageQuery.php b/src/applications/packages/query/PhabricatorPackagesPackageQuery.php new file mode 100644 index 0000000000..66f1d99840 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesPackageQuery.php @@ -0,0 +1,152 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withPublisherPHIDs(array $phids) { + $this->publisherPHIDs = $phids; + return $this; + } + + public function withPackageKeys(array $keys) { + $this->packageKeys = $keys; + return $this; + } + + public function withFullKeys(array $keys) { + $this->fullKeys = $keys; + return $this; + } + + public function newResultObject() { + return new PhabricatorPackagesPackage(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'p.id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'p.phid IN (%Ls)', + $this->phids); + } + + if ($this->publisherPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'p.phid IN (%Ls)', + $this->publisherPHIDs); + } + + if ($this->packageKeys !== null) { + $where[] = qsprintf( + $conn, + 'p.packageKey IN (%Ls)', + $this->packageKeys); + } + + if ($this->fullKeys !== null) { + $parts = array(); + foreach ($this->fullKeys as $full_key) { + $key_parts = explode('/', $full_key, 2); + + if (count($key_parts) != 2) { + continue; + } + + $parts[] = qsprintf( + $conn, + '(u.publisherKey = %s AND p.packageKey = %s)', + $key_parts[0], + $key_parts[1]); + } + + // If none of the full keys we were provided were valid, we don't + // match any results. + if (!$parts) { + throw new PhabricatorEmptyQueryException(); + } + + $where[] = qsprintf( + $conn, + '%Q', + implode(' OR ', $parts)); + } + + return $where; + } + + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + + $join_publisher = ($this->fullKeys !== null); + if ($join_publisher) { + $publisher_table = new PhabricatorPackagesPublisher(); + + $joins[] = qsprintf( + $conn, + 'JOIN %T u ON u.phid = p.publisherPHID', + $publisher_table->getTableName()); + } + + return $joins; + } + + protected function willFilterPage(array $packages) { + $publisher_phids = mpull($packages, 'getPublisherPHID'); + + $publishers = id(new PhabricatorPackagesPublisherQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withPHIDs($publisher_phids) + ->execute(); + $publishers = mpull($publishers, null, 'getPHID'); + + foreach ($packages as $key => $package) { + $publisher = idx($publishers, $package->getPublisherPHID()); + + if (!$publisher) { + unset($packages[$key]); + $this->didRejectResult($package); + continue; + } + + $package->attachPublisher($publisher); + } + + return $packages; + } + + protected function getPrimaryTableAlias() { + return 'p'; + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php b/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php new file mode 100644 index 0000000000..38fd264276 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesPackageSearchEngine.php @@ -0,0 +1,77 @@ +newQuery(); + + return $query; + } + + protected function buildCustomSearchFields() { + return array(); + } + + protected function getURI($path) { + return '/packages/package/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Packages'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $packages, + PhabricatorSavedQuery $query, + array $handles) { + + assert_instances_of($packages, 'PhabricatorPackagesPackage'); + + $viewer = $this->requireViewer(); + + $list = id(new PHUIObjectItemListView()) + ->setViewer($viewer); + foreach ($packages as $package) { + $item = id(new PHUIObjectItemView()) + ->setObjectName($package->getFullKey()) + ->setHeader($package->getName()) + ->setHref($package->getURI()); + + $list->addItem($item); + } + + return id(new PhabricatorApplicationSearchResultView()) + ->setObjectList($list) + ->setNoDataString(pht('No packages found.')); + } + +} diff --git a/src/applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php b/src/applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php new file mode 100644 index 0000000000..146f138119 --- /dev/null +++ b/src/applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php @@ -0,0 +1,10 @@ + true, + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'text64', + 'packageKey' => 'sort64', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_package' => array( + 'columns' => array('publisherPHID', 'packageKey'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorPackagesPackagePHIDType::TYPECONST); + } + + public function getURI() { + $full_key = $this->getFullKey(); + return "/package/{$full_key}/"; + } + + public function getFullKey() { + $publisher = $this->getPublisher(); + $publisher_key = $publisher->getPublisherKey(); + $package_key = $this->getPackageKey(); + return "{$publisher_key}/{$package_key}"; + } + + public function attachPublisher(PhabricatorPackagesPublisher $publisher) { + $this->publisher = $publisher; + return $this; + } + + public function getPublisher() { + return $this->assertAttached($this->publisher); + } + + public static function assertValidPackageName($value) { + $length = phutil_utf8_strlen($value); + if (!$length) { + throw new Exception( + pht( + 'Package name "%s" is not valid: package names are required.', + $value)); + } + + $max_length = 64; + if ($length > $max_length) { + throw new Exception( + pht( + 'Package name "%s" is not valid: package names must not be '. + 'more than %s characters long.', + $value, + new PhutilNumber($max_length))); + } + } + + public static function assertValidPackageKey($value) { + $length = phutil_utf8_strlen($value); + if (!$length) { + throw new Exception( + pht( + 'Package key "%s" is not valid: package keys are required.', + $value)); + } + + $max_length = 64; + if ($length > $max_length) { + throw new Exception( + pht( + 'Package key "%s" is not valid: package keys must not be '. + 'more than %s characters long.', + $value, + new PhutilNumber($max_length))); + } + + if (!preg_match('/^[a-z]+\z/', $value)) { + throw new Exception( + pht( + 'Package key "%s" is not valid: package keys may only contain '. + 'lowercase latin letters.', + $value)); + } + } + + +/* -( PhabricatorSubscribableInterface )----------------------------------- */ + + + public function isAutomaticallySubscribed($phid) { + return false; + } + + +/* -( Policy Interface )--------------------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $user) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $this->delete(); + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorPackagesPackageEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorPackagesPackageTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of the package.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('packageKey') + ->setType('string') + ->setDescription(pht('The unique key of the package.')), + ); + } + + public function getFieldValuesForConduit() { + $publisher = $this->getPublisher(); + + $publisher_map = array( + 'id' => (int)$publisher->getID(), + 'phid' => $publisher->getPHID(), + 'name' => $publisher->getName(), + 'publisherKey' => $publisher->getPublisherKey(), + ); + + return array( + 'name' => $this->getName(), + 'packageKey' => $this->getPackageKey(), + 'fullKey' => $this->getFullKey(), + 'publisher' => $publisher_map, + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + +} diff --git a/src/applications/packages/storage/PhabricatorPackagesPackageTransaction.php b/src/applications/packages/storage/PhabricatorPackagesPackageTransaction.php new file mode 100644 index 0000000000..29a6b74051 --- /dev/null +++ b/src/applications/packages/storage/PhabricatorPackagesPackageTransaction.php @@ -0,0 +1,18 @@ +delete(); + $viewer = $engine->getViewer(); + + $this->openTransaction(); + + $packages = id(new PhabricatorPackagesPackageQuery()) + ->setViewer($viewer) + ->withPublisherPHIDs(array($this->getPHID())) + ->execute(); + foreach ($packages as $package) { + $engine->destroyObject($package); + } + + $this->delete(); + + $this->saveTransaction(); } diff --git a/src/applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php b/src/applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php new file mode 100644 index 0000000000..e8ca7d4c7f --- /dev/null +++ b/src/applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php @@ -0,0 +1,35 @@ +getViewer(); + $raw_query = $this->getRawQuery(); + + $publisher_query = id(new PhabricatorPackagesPublisherQuery()); + $publishers = $this->executeQuery($publisher_query); + + $results = array(); + foreach ($publishers as $publisher) { + $results[] = id(new PhabricatorTypeaheadResult()) + ->setName($publisher->getName()) + ->setPHID($publisher->getPHID()); + } + + return $this->filterResultsAgainstTokens($results); + } + +} diff --git a/src/applications/packages/xaction/package/PhabricatorPackagesPackageKeyTransaction.php b/src/applications/packages/xaction/package/PhabricatorPackagesPackageKeyTransaction.php new file mode 100644 index 0000000000..6029b8f4af --- /dev/null +++ b/src/applications/packages/xaction/package/PhabricatorPackagesPackageKeyTransaction.php @@ -0,0 +1,47 @@ +getPackageKey(); + } + + public function applyInternalEffects($object, $value) { + $object->setPackageKey($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht( + 'Each package provided by a publisher must have a '. + 'unique package key.')); + return $errors; + } + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + $errors[] = $this->newInvalidError( + pht('Once a package is created, its key can not be changed.'), + $xaction); + } + } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + try { + PhabricatorPackagesPackage::assertValidPackageKey($value); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError($ex->getMessage(), $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php b/src/applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php new file mode 100644 index 0000000000..0496911e57 --- /dev/null +++ b/src/applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php @@ -0,0 +1,54 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + return pht( + '%s changed the name of this package from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the name for %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht('Packages must have a name.')); + return $errors; + } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + try { + PhabricatorPackagesPackage::assertValidPackageName($value); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError($ex->getMessage(), $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php b/src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php new file mode 100644 index 0000000000..a3be870fdd --- /dev/null +++ b/src/applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php @@ -0,0 +1,66 @@ +getPublisherPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setPublisherPHID($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { + $errors[] = $this->newRequiredError( + pht( + 'You must select a publisher when creating a package.')); + return $errors; + } + + if (!$this->isNewObject()) { + foreach ($xactions as $xaction) { + $errors[] = $this->newInvalidError( + pht('Once a package is created, its publisher can not be changed.'), + $xaction); + } + } + + $viewer = $this->getActor(); + foreach ($xactions as $xaction) { + $publisher_phid = $xaction->getNewValue(); + + $publisher = id(new PhabricatorPackagesPublisherQuery()) + ->setViewer($viewer) + ->withPHIDs(array($publisher_phid)) + ->setRaisePolicyExceptions(false) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + if (!$publisher) { + $errors[] = $this->newInvalidError( + pht( + 'Publisher "%s" is invalid: the publisher must exist and you '. + 'must have permission to edit it in order to create a new '. + 'package.', + $publisher_phid), + $xaction); + continue; + } + + $object->attachPublisher($publisher); + } + + return $errors; + } + +} diff --git a/src/applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php b/src/applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php new file mode 100644 index 0000000000..08cbb113b4 --- /dev/null +++ b/src/applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php @@ -0,0 +1,4 @@ +