Page MenuHomePhabricator

D9204.diff
No OneTemporary

D9204.diff

diff --git a/resources/sql/autopatches/20150601.spaces.1.namespace.sql b/resources/sql/autopatches/20150601.spaces.1.namespace.sql
new file mode 100644
--- /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
--- /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
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1318,6 +1318,7 @@
'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 @@
'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 @@
'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController',
'PhabricatorApplicationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorApplicationSearchController' => 'PhabricatorSearchBaseController',
+ 'PhabricatorApplicationSpaces' => 'PhabricatorApplication',
'PhabricatorApplicationStatusView' => 'AphrontView',
'PhabricatorApplicationTransaction' => array(
'PhabricatorLiskDAO',
@@ -6027,6 +6045,26 @@
'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
--- /dev/null
+++ b/src/applications/spaces/application/PhabricatorApplicationSpaces.php
@@ -0,0 +1,67 @@
+<?php
+
+final class PhabricatorApplicationSpaces extends PhabricatorApplication {
+
+ public function getBaseURI() {
+ return '/spaces/';
+ }
+
+ public function getName() {
+ return pht('Spaces');
+ }
+
+ public function getShortDescription() {
+ return pht('Policy Namespaces');
+ }
+
+ public function getFontIcon() {
+ return 'fa-compass';
+ }
+
+ public function getTitleGlyph() {
+ return "\xE2\x97\x8B";
+ }
+
+ public function getFlavorText() {
+ return pht('Control access to groups of objects.');
+ }
+
+ public function getApplicationGroup() {
+ return self::GROUP_UTILITIES;
+ }
+
+ public function canUninstall() {
+ return true;
+ }
+
+ public function isPrototype() {
+ return true;
+ }
+
+ public function getRoutes() {
+ return array(
+ '/S(?P<id>[1-9]\d*)' => 'PhabricatorSpacesViewController',
+ '/spaces/' => array(
+ '(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorSpacesListController',
+ 'create/' => 'PhabricatorSpacesEditController',
+ 'edit/(?:(?P<id>\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
--- /dev/null
+++ b/src/applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php
@@ -0,0 +1,16 @@
+<?php
+
+final class PhabricatorSpacesCapabilityCreateSpaces
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'spaces.create';
+
+ public function getCapabilityName() {
+ return pht('Can Create Spaces');
+ }
+
+ public function describeCapabilityRejection() {
+ return pht('You do not have permission to create spaces.');
+ }
+
+}
diff --git a/src/applications/spaces/capability/PhabricatorSpacesCapabilityDefaultEdit.php b/src/applications/spaces/capability/PhabricatorSpacesCapabilityDefaultEdit.php
new file mode 100644
--- /dev/null
+++ b/src/applications/spaces/capability/PhabricatorSpacesCapabilityDefaultEdit.php
@@ -0,0 +1,12 @@
+<?php
+
+final class PhabricatorSpacesCapabilityDefaultEdit
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'spaces.default.edit';
+
+ public function getCapabilityName() {
+ return pht('Default Edit Policy');
+ }
+
+}
diff --git a/src/applications/spaces/capability/PhabricatorSpacesCapabilityDefaultView.php b/src/applications/spaces/capability/PhabricatorSpacesCapabilityDefaultView.php
new file mode 100644
--- /dev/null
+++ b/src/applications/spaces/capability/PhabricatorSpacesCapabilityDefaultView.php
@@ -0,0 +1,16 @@
+<?php
+
+final class PhabricatorSpacesCapabilityDefaultView
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'spaces.default.view';
+
+ public function getCapabilityName() {
+ return pht('Default View Policy');
+ }
+
+ public function shouldAllowPublicPolicySetting() {
+ return true;
+ }
+
+}
diff --git a/src/applications/spaces/controller/PhabricatorSpacesController.php b/src/applications/spaces/controller/PhabricatorSpacesController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/spaces/controller/PhabricatorSpacesController.php
@@ -0,0 +1,3 @@
+<?php
+
+abstract class PhabricatorSpacesController extends PhabricatorController {}
diff --git a/src/applications/spaces/controller/PhabricatorSpacesEditController.php b/src/applications/spaces/controller/PhabricatorSpacesEditController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/spaces/controller/PhabricatorSpacesEditController.php
@@ -0,0 +1,174 @@
+<?php
+
+final class PhabricatorSpacesEditController
+ extends PhabricatorSpacesController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->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
--- /dev/null
+++ b/src/applications/spaces/controller/PhabricatorSpacesListController.php
@@ -0,0 +1,52 @@
+<?php
+
+final class PhabricatorSpacesListController
+ extends PhabricatorSpacesController {
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ public function handleRequest(AphrontRequest $request) {
+ $request = $this->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
--- /dev/null
+++ b/src/applications/spaces/controller/PhabricatorSpacesViewController.php
@@ -0,0 +1,104 @@
+<?php
+
+final class PhabricatorSpacesViewController
+ extends PhabricatorSpacesController {
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->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
--- /dev/null
+++ b/src/applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php
@@ -0,0 +1,132 @@
+<?php
+
+final class PhabricatorSpacesNamespaceEditor
+ extends PhabricatorApplicationTransactionEditor {
+
+ public function getEditorApplicationClass() {
+ return pht('PhabricatorSpacesApplication');
+ }
+
+ public function getEditorObjectsDescription() {
+ return pht('Spaces');
+ }
+
+ public function getTransactionTypes() {
+ $types = parent::getTransactionTypes();
+
+ $types[] = PhabricatorSpacesNamespaceTransaction::TYPE_NAME;
+ $types[] = PhabricatorSpacesNamespaceTransaction::TYPE_DEFAULT;
+
+ $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
+ $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
+
+ return $types;
+ }
+
+ protected function getCustomTransactionOldValue(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ switch ($xaction->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
--- /dev/null
+++ b/src/applications/spaces/interface/PhabricatorSpacesInterface.php
@@ -0,0 +1,3 @@
+<?php
+
+interface PhabricatorSpacesInterface extends PhabricatorPHIDInterface {}
diff --git a/src/applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php b/src/applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php
@@ -0,0 +1,64 @@
+<?php
+
+final class PhabricatorSpacesNamespacePHIDType
+ extends PhabricatorPHIDType {
+
+ const TYPECONST = 'SPCE';
+
+ public function getTypeName() {
+ return pht('Space');
+ }
+
+ public function newObject() {
+ return new PhabricatorSpacesNamespace();
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new PhabricatorSpacesNamespaceQuery())
+ ->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
--- /dev/null
+++ b/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php
@@ -0,0 +1,77 @@
+<?php
+
+final class PhabricatorSpacesNamespaceQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+ private $isDefaultNamespace;
+
+ public function withIDs(array $ids) {
+ $this->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
--- /dev/null
+++ b/src/applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php
@@ -0,0 +1,81 @@
+<?php
+
+final class PhabricatorSpacesNamespaceSearchEngine
+ extends PhabricatorApplicationSearchEngine {
+
+ public function getApplicationClassName() {
+ return 'PhabricatorSpacesApplication';
+ }
+
+ public function getResultTypeDescription() {
+ return pht('Spaces');
+ }
+
+ public function buildSavedQueryFromRequest(AphrontRequest $request) {
+ $saved = new PhabricatorSavedQuery();
+
+ return $saved;
+ }
+
+ public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
+ $query = id(new PhabricatorSpacesNamespaceQuery());
+
+ return $query;
+ }
+
+ public function buildSearchForm(
+ AphrontFormView $form,
+ PhabricatorSavedQuery $saved_query) {}
+
+ protected function getURI($path) {
+ return '/spaces/'.$path;
+ }
+
+ public function getBuiltinQueryNames() {
+ $names = array(
+ 'all' => 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
--- /dev/null
+++ b/src/applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhabricatorSpacesNamespaceTransactionQuery
+ extends PhabricatorApplicationTransactionQuery {
+
+ public function getTemplateApplicationTransaction() {
+ return new PhabricatorSpacesNamespaceTransaction();
+ }
+
+}
diff --git a/src/applications/spaces/storage/PhabricatorSpacesDAO.php b/src/applications/spaces/storage/PhabricatorSpacesDAO.php
new file mode 100644
--- /dev/null
+++ b/src/applications/spaces/storage/PhabricatorSpacesDAO.php
@@ -0,0 +1,9 @@
+<?php
+
+abstract class PhabricatorSpacesDAO extends PhabricatorLiskDAO {
+
+ public function getApplicationName() {
+ return 'spaces';
+ }
+
+}
diff --git a/src/applications/spaces/storage/PhabricatorSpacesNamespace.php b/src/applications/spaces/storage/PhabricatorSpacesNamespace.php
new file mode 100644
--- /dev/null
+++ b/src/applications/spaces/storage/PhabricatorSpacesNamespace.php
@@ -0,0 +1,106 @@
+<?php
+
+final class PhabricatorSpacesNamespace
+ extends PhabricatorSpacesDAO
+ implements
+ PhabricatorPolicyInterface,
+ PhabricatorApplicationTransactionInterface {
+
+ protected $namespaceName;
+ protected $viewPolicy;
+ protected $editPolicy;
+ protected $isDefaultNamespace;
+
+ public static function initializeNewNamespace(PhabricatorUser $actor) {
+ $app = id(new PhabricatorApplicationQuery())
+ ->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
--- /dev/null
+++ b/src/applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php
@@ -0,0 +1,49 @@
+<?php
+
+final class PhabricatorSpacesNamespaceTransaction
+ extends PhabricatorApplicationTransaction {
+
+ const TYPE_NAME = 'spaces:name';
+ const TYPE_DEFAULT = 'spaces:default';
+
+ public function getApplicationName() {
+ return 'spaces';
+ }
+
+ public function getApplicationTransactionType() {
+ return PhabricatorSpacesNamespacePHIDType::TYPECONST;
+ }
+
+ public function getApplicationTransactionCommentObject() {
+ return null;
+ }
+
+ public function getTitle() {
+ $old = $this->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
--- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -105,6 +105,7 @@
'db.fund' => array(),
'db.almanac' => array(),
'db.multimeter' => array(),
+ 'db.spaces' => array(),
'0000.legacy.sql' => array(
'legacy' => 0,
),

File Metadata

Mime Type
text/plain
Expires
Sat, May 11, 10:48 AM (3 w, 3 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/vo/ku/4hvxn2myt37wi57j
Default Alt Text
D9204.diff (38 KB)

Event Timeline