From 541b4c86b4054e2a76cb94758fe74d7dab16f195 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 1 Jun 2015 11:28:38 -0700 Subject: [PATCH] Add "Spaces", an application for managing policy namespaces Summary: Ref T3820. This doesn't actually do anything yet, but dumps in all the plumbing. Test Plan: {F156989} {F156990} {F156991} Reviewers: btrahan Reviewed By: btrahan Subscribers: eadler, wienczny, jdloft, devurandom, thz, hwinkel, 20after4, sascha-egerer, seporaitis, joshuaspence, chad, epriestley Maniphest Tasks: T3820 Differential Revision: https://secure.phabricator.com/D9204 --- .../20150601.spaces.1.namespace.sql | 12 ++ .../autopatches/20150601.spaces.2.xaction.sql | 19 ++ src/__phutil_library_map__.php | 38 ++++ .../PhabricatorApplicationSpaces.php | 67 +++++++ ...habricatorSpacesCapabilityCreateSpaces.php | 16 ++ ...PhabricatorSpacesCapabilityDefaultEdit.php | 12 ++ ...PhabricatorSpacesCapabilityDefaultView.php | 16 ++ .../PhabricatorSpacesController.php | 3 + .../PhabricatorSpacesEditController.php | 174 ++++++++++++++++++ .../PhabricatorSpacesListController.php | 52 ++++++ .../PhabricatorSpacesViewController.php | 104 +++++++++++ .../PhabricatorSpacesNamespaceEditor.php | 132 +++++++++++++ .../interface/PhabricatorSpacesInterface.php | 3 + .../PhabricatorSpacesNamespacePHIDType.php | 64 +++++++ .../query/PhabricatorSpacesNamespaceQuery.php | 77 ++++++++ ...PhabricatorSpacesNamespaceSearchEngine.php | 81 ++++++++ ...ricatorSpacesNamespaceTransactionQuery.php | 10 + .../spaces/storage/PhabricatorSpacesDAO.php | 9 + .../storage/PhabricatorSpacesNamespace.php | 106 +++++++++++ .../PhabricatorSpacesNamespaceTransaction.php | 49 +++++ .../patch/PhabricatorBuiltinPatchList.php | 1 + 21 files changed, 1045 insertions(+) create mode 100644 resources/sql/autopatches/20150601.spaces.1.namespace.sql create mode 100644 resources/sql/autopatches/20150601.spaces.2.xaction.sql create mode 100644 src/applications/spaces/application/PhabricatorApplicationSpaces.php create mode 100644 src/applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php create mode 100644 src/applications/spaces/capability/PhabricatorSpacesCapabilityDefaultEdit.php create mode 100644 src/applications/spaces/capability/PhabricatorSpacesCapabilityDefaultView.php create mode 100644 src/applications/spaces/controller/PhabricatorSpacesController.php create mode 100644 src/applications/spaces/controller/PhabricatorSpacesEditController.php create mode 100644 src/applications/spaces/controller/PhabricatorSpacesListController.php create mode 100644 src/applications/spaces/controller/PhabricatorSpacesViewController.php create mode 100644 src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php create mode 100644 src/applications/spaces/interface/PhabricatorSpacesInterface.php create mode 100644 src/applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php create mode 100644 src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php create mode 100644 src/applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php create mode 100644 src/applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php create mode 100644 src/applications/spaces/storage/PhabricatorSpacesDAO.php create mode 100644 src/applications/spaces/storage/PhabricatorSpacesNamespace.php create mode 100644 src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php diff --git a/resources/sql/autopatches/20150601.spaces.1.namespace.sql b/resources/sql/autopatches/20150601.spaces.1.namespace.sql new file mode 100644 index 0000000000..302e5e8277 --- /dev/null +++ b/resources/sql/autopatches/20150601.spaces.1.namespace.sql @@ -0,0 +1,12 @@ +CREATE TABLE {$NAMESPACE}_spaces.spaces_namespace ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + namespaceName VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT}, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + isDefaultNamespace BOOL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + UNIQUE KEY `key_default` (isDefaultNamespace) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20150601.spaces.2.xaction.sql b/resources/sql/autopatches/20150601.spaces.2.xaction.sql new file mode 100644 index 0000000000..4222593c6b --- /dev/null +++ b/resources/sql/autopatches/20150601.spaces.2.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_spaces.spaces_namespacetransaction ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + 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), + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + oldValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + newValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + contentSource LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + metadata LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + 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 a79ccf1313..3595b2d7f5 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1318,6 +1318,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationSearchController' => 'applications/search/controller/PhabricatorApplicationSearchController.php', 'PhabricatorApplicationSearchEngine' => 'applications/search/engine/PhabricatorApplicationSearchEngine.php', 'PhabricatorApplicationSearchResultsControllerInterface' => 'applications/search/interface/PhabricatorApplicationSearchResultsControllerInterface.php', + 'PhabricatorApplicationSpaces' => 'applications/spaces/application/PhabricatorApplicationSpaces.php', 'PhabricatorApplicationStatusView' => 'applications/meta/view/PhabricatorApplicationStatusView.php', 'PhabricatorApplicationTransaction' => 'applications/transactions/storage/PhabricatorApplicationTransaction.php', 'PhabricatorApplicationTransactionComment' => 'applications/transactions/storage/PhabricatorApplicationTransactionComment.php', @@ -2561,6 +2562,22 @@ phutil_register_library_map(array( 'PhabricatorSlugTestCase' => 'infrastructure/util/__tests__/PhabricatorSlugTestCase.php', 'PhabricatorSortTableUIExample' => 'applications/uiexample/examples/PhabricatorSortTableUIExample.php', 'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php', + 'PhabricatorSpacesCapabilityCreateSpaces' => 'applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php', + 'PhabricatorSpacesCapabilityDefaultEdit' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultEdit.php', + 'PhabricatorSpacesCapabilityDefaultView' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultView.php', + 'PhabricatorSpacesController' => 'applications/spaces/controller/PhabricatorSpacesController.php', + 'PhabricatorSpacesDAO' => 'applications/spaces/storage/PhabricatorSpacesDAO.php', + 'PhabricatorSpacesEditController' => 'applications/spaces/controller/PhabricatorSpacesEditController.php', + 'PhabricatorSpacesInterface' => 'applications/spaces/interface/PhabricatorSpacesInterface.php', + 'PhabricatorSpacesListController' => 'applications/spaces/controller/PhabricatorSpacesListController.php', + 'PhabricatorSpacesNamespace' => 'applications/spaces/storage/PhabricatorSpacesNamespace.php', + 'PhabricatorSpacesNamespaceEditor' => 'applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php', + 'PhabricatorSpacesNamespacePHIDType' => 'applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php', + 'PhabricatorSpacesNamespaceQuery' => 'applications/spaces/query/PhabricatorSpacesNamespaceQuery.php', + 'PhabricatorSpacesNamespaceSearchEngine' => 'applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php', + 'PhabricatorSpacesNamespaceTransaction' => 'applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php', + 'PhabricatorSpacesNamespaceTransactionQuery' => 'applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php', + 'PhabricatorSpacesViewController' => 'applications/spaces/controller/PhabricatorSpacesViewController.php', 'PhabricatorStandardCustomField' => 'infrastructure/customfield/standard/PhabricatorStandardCustomField.php', 'PhabricatorStandardCustomFieldBool' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php', 'PhabricatorStandardCustomFieldCredential' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php', @@ -4647,6 +4664,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorApplicationSearchController' => 'PhabricatorSearchBaseController', + 'PhabricatorApplicationSpaces' => 'PhabricatorApplication', 'PhabricatorApplicationStatusView' => 'AphrontView', 'PhabricatorApplicationTransaction' => array( 'PhabricatorLiskDAO', @@ -6027,6 +6045,26 @@ phutil_register_library_map(array( 'PhabricatorSlugTestCase' => 'PhabricatorTestCase', 'PhabricatorSortTableUIExample' => 'PhabricatorUIExample', 'PhabricatorSourceCodeView' => 'AphrontView', + 'PhabricatorSpacesCapabilityCreateSpaces' => 'PhabricatorPolicyCapability', + 'PhabricatorSpacesCapabilityDefaultEdit' => 'PhabricatorPolicyCapability', + 'PhabricatorSpacesCapabilityDefaultView' => 'PhabricatorPolicyCapability', + 'PhabricatorSpacesController' => 'PhabricatorController', + 'PhabricatorSpacesDAO' => 'PhabricatorLiskDAO', + 'PhabricatorSpacesEditController' => 'PhabricatorSpacesController', + 'PhabricatorSpacesInterface' => 'PhabricatorPHIDInterface', + 'PhabricatorSpacesListController' => 'PhabricatorSpacesController', + 'PhabricatorSpacesNamespace' => array( + 'PhabricatorSpacesDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', + ), + 'PhabricatorSpacesNamespaceEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorSpacesNamespacePHIDType' => 'PhabricatorPHIDType', + 'PhabricatorSpacesNamespaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorSpacesNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorSpacesNamespaceTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorSpacesNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorSpacesViewController' => 'PhabricatorSpacesController', 'PhabricatorStandardCustomField' => 'PhabricatorCustomField', 'PhabricatorStandardCustomFieldBool' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldCredential' => 'PhabricatorStandardCustomField', diff --git a/src/applications/spaces/application/PhabricatorApplicationSpaces.php b/src/applications/spaces/application/PhabricatorApplicationSpaces.php new file mode 100644 index 0000000000..d0abff89de --- /dev/null +++ b/src/applications/spaces/application/PhabricatorApplicationSpaces.php @@ -0,0 +1,67 @@ +[1-9]\d*)' => 'PhabricatorSpacesViewController', + '/spaces/' => array( + '(?:query/(?P[^/]+)/)?' => 'PhabricatorSpacesListController', + 'create/' => 'PhabricatorSpacesEditController', + 'edit/(?:(?P\d+)/)?' => 'PhabricatorSpacesEditController', + ), + ); + } + + protected function getCustomCapabilities() { + return array( + PhabricatorSpacesCapabilityCreateSpaces::CAPABILITY => array( + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), + PhabricatorSpacesCapabilityDefaultView::CAPABILITY => array( + 'caption' => pht('Default view policy for newly created spaces.'), + ), + PhabricatorSpacesCapabilityDefaultEdit::CAPABILITY => array( + 'caption' => pht('Default edit policy for newly created spaces.'), + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), + ); + } + +} diff --git a/src/applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php b/src/applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php new file mode 100644 index 0000000000..4cecae1d88 --- /dev/null +++ b/src/applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php @@ -0,0 +1,16 @@ +getUser(); + + $make_default = false; + + $id = $request->getURIData('id'); + if ($id) { + $space = id(new PhabricatorSpacesNamespaceQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$space) { + return new Aphront404Response(); + } + + $is_new = false; + $cancel_uri = '/'.$space->getMonogram(); + + $header_text = pht('Edit %s', $space->getNamespaceName()); + $title = pht('Edit Space'); + $button_text = pht('Save Changes'); + } else { + $this->requireApplicationCapability( + PhabricatorSpacesCapabilityCreateSpaces::CAPABILITY); + + $space = PhabricatorSpacesNamespace::initializeNewNamespace($viewer); + + $is_new = true; + $cancel_uri = $this->getApplicationURI(); + + $header_text = pht('Create Space'); + $title = pht('Create Space'); + $button_text = pht('Create Space'); + + $default = id(new PhabricatorSpacesNamespaceQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withIsDefaultNamespace(true) + ->execute(); + if (!$default) { + $make_default = true; + } + } + + $validation_exception = null; + $e_name = true; + $v_name = $space->getNamespaceName(); + $v_view = $space->getViewPolicy(); + $v_edit = $space->getEditPolicy(); + + if ($request->isFormPost()) { + $xactions = array(); + $e_name = null; + + $v_name = $request->getStr('name'); + $v_view = $request->getStr('viewPolicy'); + $v_edit = $request->getStr('editPolicy'); + + $type_name = PhabricatorSpacesNamespaceTransaction::TYPE_NAME; + $type_default = PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT; + $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; + $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; + + $xactions[] = id(new PhabricatorSpacesNamespaceTransaction()) + ->setTransactionType($type_name) + ->setNewValue($v_name); + + $xactions[] = id(new PhabricatorSpacesNamespaceTransaction()) + ->setTransactionType($type_view) + ->setNewValue($v_view); + + $xactions[] = id(new PhabricatorSpacesNamespaceTransaction()) + ->setTransactionType($type_edit) + ->setNewValue($v_edit); + + if ($make_default) { + $xactions[] = id(new PhabricatorSpacesNamespaceTransaction()) + ->setTransactionType($type_default) + ->setNewValue(1); + } + + $editor = id(new PhabricatorSpacesNamespaceEditor()) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContentSourceFromRequest($request); + + try { + $editor->applyTransactions($space, $xactions); + + return id(new AphrontRedirectResponse()) + ->setURI('/'.$space->getMonogram()); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + + $e_name = $ex->getShortMessage($type_name); + } + } + + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->setObject($space) + ->execute(); + + $form = id(new AphrontFormView()) + ->setUser($viewer); + + if ($make_default) { + $form->appendRemarkupInstructions( + pht( + 'NOTE: You are creating the **default space**. All existing '. + 'objects will be put into this space. You must create a default '. + 'space before you can create other spaces.')); + } + + $form + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Name')) + ->setName('name') + ->setValue($v_name) + ->setError($e_name)) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setUser($viewer) + ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) + ->setPolicyObject($space) + ->setPolicies($policies) + ->setValue($v_view) + ->setName('viewPolicy')) + ->appendChild( + id(new AphrontFormPolicyControl()) + ->setUser($viewer) + ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) + ->setPolicyObject($space) + ->setPolicies($policies) + ->setValue($v_edit) + ->setName('editPolicy')) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue($button_text) + ->addCancelButton($cancel_uri)); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($header_text) + ->setValidationException($validation_exception) + ->appendChild($form); + + $crumbs = $this->buildApplicationCrumbs(); + if (!$is_new) { + $crumbs->addTextCrumb( + $space->getMonogram(), + $cancel_uri); + } + $crumbs->addTextCrumb($title); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + )); + } +} diff --git a/src/applications/spaces/controller/PhabricatorSpacesListController.php b/src/applications/spaces/controller/PhabricatorSpacesListController.php new file mode 100644 index 0000000000..0f6ea5be15 --- /dev/null +++ b/src/applications/spaces/controller/PhabricatorSpacesListController.php @@ -0,0 +1,52 @@ +getRequest(); + $controller = id(new PhabricatorApplicationSearchController($request)) + ->setQueryKey($request->getURIData('queryKey')) + ->setSearchEngine(new PhabricatorSpacesNamespaceSearchEngine()) + ->setNavigation($this->buildSideNavView()); + + return $this->delegateToController($controller); + } + + public function buildSideNavView($for_app = false) { + $user = $this->getRequest()->getUser(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + id(new PhabricatorSpacesNamespaceSearchEngine()) + ->setViewer($user) + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); + + return $nav; + } + + public function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $can_create = $this->hasApplicationCapability( + PhabricatorSpacesCapabilityCreateSpaces::CAPABILITY); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Space')) + ->setHref($this->getApplicationURI('create/')) + ->setIcon('fa-plus-square') + ->setDisabled(!$can_create) + ->setWorkflow(!$can_create)); + + return $crumbs; + } + +} diff --git a/src/applications/spaces/controller/PhabricatorSpacesViewController.php b/src/applications/spaces/controller/PhabricatorSpacesViewController.php new file mode 100644 index 0000000000..911549234f --- /dev/null +++ b/src/applications/spaces/controller/PhabricatorSpacesViewController.php @@ -0,0 +1,104 @@ +getViewer(); + + $space = id(new PhabricatorSpacesNamespaceQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->executeOne(); + if (!$space) { + return new Aphront404Response(); + } + + $action_list = $this->buildActionListView($space); + $property_list = $this->buildPropertyListView($space); + $property_list->setActionList($action_list); + + $xactions = id(new PhabricatorSpacesNamespaceTransactionQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($space->getPHID())) + ->execute(); + + $timeline = $this->buildTransactionTimeline( + $space, + new PhabricatorSpacesNamespaceTransactionQuery()); + $timeline->setShouldTerminate(true); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setHeader($space->getNamespaceName()) + ->setPolicyObject($space); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($property_list); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($space->getMonogram()); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + $timeline, + ), + array( + 'title' => array($space->getMonogram(), $space->getNamespaceName()), + )); + } + + private function buildPropertyListView(PhabricatorSpacesNamespace $space) { + $viewer = $this->getRequest()->getUser(); + + $list = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $list->addProperty( + pht('Default Space'), + $space->getIsDefaultNamespace() + ? pht('Yes') + : pht('No')); + + $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( + $viewer, + $space); + + $list->addProperty( + pht('Editable By'), + $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); + + return $list; + } + + private function buildActionListView(PhabricatorSpacesNamespace $space) { + $viewer = $this->getRequest()->getUser(); + + $list = id(new PhabricatorActionListView()) + ->setUser($viewer) + ->setObjectURI('/'.$space->getMonogram()); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $space, + PhabricatorPolicyCapability::CAN_EDIT); + + $list->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Space')) + ->setIcon('fa-pencil') + ->setHref($this->getApplicationURI('edit/'.$space->getID().'/')) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit)); + + return $list; + } + +} diff --git a/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php b/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php new file mode 100644 index 0000000000..23b01531e9 --- /dev/null +++ b/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php @@ -0,0 +1,132 @@ +getTransactionType()) { + case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: + $name = $object->getNamespaceName(); + if (!strlen($name)) { + return null; + } + return $name; + case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: + return $object->getIsDefaultNamespace() ? 1 : null; + case PhabricatorTransactions::TYPE_VIEW_POLICY: + return $object->getViewPolicy(); + case PhabricatorTransactions::TYPE_EDIT_POLICY: + return $object->getEditPolicy(); + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + return $xaction->getNewValue(); + case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: + return $xaction->getNewValue() ? 1 : null; + } + + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + $new = $xaction->getNewValue(); + + switch ($xaction->getTransactionType()) { + case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: + $object->setNamespaceName($new); + return; + case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: + $object->setIsDefaultNamespace($new ? 1 : null); + return; + case PhabricatorTransactions::TYPE_VIEW_POLICY: + $object->setViewPolicy($new); + return; + case PhabricatorTransactions::TYPE_EDIT_POLICY: + $object->setEditPolicy($new); + return; + } + + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: + case PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT: + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case PhabricatorSpacesNamespaceTransaction::TYPE_NAME: + $missing = $this->validateIsEmptyTextField( + $object->getNamespaceName(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Spaces must have a name.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } + break; + } + + return $errors; + } + +} diff --git a/src/applications/spaces/interface/PhabricatorSpacesInterface.php b/src/applications/spaces/interface/PhabricatorSpacesInterface.php new file mode 100644 index 0000000000..713eb69d46 --- /dev/null +++ b/src/applications/spaces/interface/PhabricatorSpacesInterface.php @@ -0,0 +1,3 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $namespace = $objects[$phid]; + $handle->setName($namespace->getNamespaceName()); + } + } + + public function canLoadNamedObject($name) { + return preg_match('/^S[1-9]\d*$/i', $name); + } + + public function loadNamedObjects( + PhabricatorObjectQuery $query, + array $names) { + + $id_map = array(); + foreach ($names as $name) { + $id = (int)substr($name, 1); + $id_map[$id][] = $name; + } + + $objects = id(new PhabricatorSpacesNamespaceQuery()) + ->setViewer($query->getViewer()) + ->withIDs(array_keys($id_map)) + ->execute(); + + $results = array(); + foreach ($objects as $id => $object) { + foreach (idx($id_map, $id, array()) as $name) { + $results[$name] = $object; + } + } + + return $results; + } + +} diff --git a/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php b/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php new file mode 100644 index 0000000000..bb27c4b664 --- /dev/null +++ b/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php @@ -0,0 +1,77 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withIsDefaultNamespace($default) { + $this->isDefaultNamespace = $default; + return $this; + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationSpaces'; + } + + protected function loadPage() { + $table = new PhabricatorSpacesNamespace(); + $conn_r = $table->establishConnection('r'); + + $rows = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($rows); + } + + protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn_r, + 'phid IN (%Ls)', + $this->phids); + } + + if ($this->isDefaultNamespace !== null) { + if ($this->isDefaultNamespace) { + $where[] = qsprintf( + $conn_r, + 'isDefaultNamespace = 1'); + } else { + $where[] = qsprintf( + $conn_r, + 'isDefaultNamespace IS NULL'); + } + } + + $where[] = $this->buildPagingClause($conn_r); + return $this->formatWhereClause($where); + } + +} diff --git a/src/applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php b/src/applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php new file mode 100644 index 0000000000..9a6269eefa --- /dev/null +++ b/src/applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php @@ -0,0 +1,81 @@ + pht('All Spaces'), + ); + + 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 $spaces, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($spaces, 'PhabricatorSpacesNamespace'); + + $viewer = $this->requireViewer(); + + $list = new PHUIObjectItemListView(); + $list->setUser($viewer); + foreach ($spaces as $space) { + $item = id(new PHUIObjectItemView()) + ->setObjectName($space->getMonogram()) + ->setHeader($space->getNamespaceName()) + ->setHref('/'.$space->getMonogram()); + + if ($space->getIsDefaultNamespace()) { + $item->addIcon('fa-certificate', pht('Default Space')); + } + + $list->addItem($item); + } + + return $list; + } + +} diff --git a/src/applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php b/src/applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php new file mode 100644 index 0000000000..34d7e43570 --- /dev/null +++ b/src/applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php @@ -0,0 +1,10 @@ +setViewer($actor) + ->withClasses(array('PhabricatorApplicationSpaces')) + ->executeOne(); + + $view_policy = $app->getPolicy( + PhabricatorSpacesCapabilityDefaultView::CAPABILITY); + $edit_policy = $app->getPolicy( + PhabricatorSpacesCapabilityDefaultEdit::CAPABILITY); + + return id(new PhabricatorSpacesNamespace()) + ->setIsDefaultNamespace(null) + ->setViewPolicy($view_policy) + ->setEditPolicy($edit_policy); + } + + public function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_COLUMN_SCHEMA => array( + 'namespaceName' => 'text255', + 'isDefaultNamespace' => 'bool?', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_default' => array( + 'columns' => array('isDefaultNamespace'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorSpacesNamespacePHIDType::TYPECONST); + } + + public function getMonogram() { + return 'S'.$this->getID(); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + 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 $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorSpacesNamespaceEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorSpacesNamespaceTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + +} diff --git a/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php b/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php new file mode 100644 index 0000000000..672cb9d8ad --- /dev/null +++ b/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php @@ -0,0 +1,49 @@ +getOldValue(); + $new = $this->getNewValue(); + + $author_phid = $this->getAuthorPHID(); + + switch ($this->getTransactionType()) { + case self::TYPE_NAME: + if ($old === null) { + return pht( + '%s created this space.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s renamed this space from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + } + case self::TYPE_DEFAULT: + return pht( + '%s made this the default space.', + $this->renderHandleLink($author_phid)); + } + + return parent::getTitle(); + } + +} diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index 581b7f9070..07f468db85 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -105,6 +105,7 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList { 'db.fund' => array(), 'db.almanac' => array(), 'db.multimeter' => array(), + 'db.spaces' => array(), '0000.legacy.sql' => array( 'legacy' => 0, ),