1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-18 12:52:42 +01:00

Support Spaces transactions

Summary:
Ref T8424. This adds crude integration with Paste's edit/view workflows: you can change the space a Paste appears in, see transactions, and get a policy callout.

Lots of rough edges and non-obviousness but it pretty much works.

Test Plan:
  - Created and updated Pastes.
  - Moved them between spaces, saw policy effects.
  - Read transactions.
  - Looked at feed.
  - Faked query to return no spaces, saw control and other stuff vanish.
  - Faked query to return no spaces, created pastes.
  - Tried to submit bad values and got errors.

Reviewers: chad, btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T8424

Differential Revision: https://secure.phabricator.com/D13159
This commit is contained in:
epriestley 2015-06-05 10:42:49 -07:00
parent 5deaeec668
commit ef90007a21
13 changed files with 306 additions and 79 deletions

View file

@ -7,7 +7,7 @@
*/
return array(
'names' => array(
'core.pkg.css' => 'e2460e8f',
'core.pkg.css' => 'd7ecac6d',
'core.pkg.js' => '3bbe23c6',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '02273347',
@ -136,7 +136,7 @@ return array(
'rsrc/css/phui/phui-fontkit.css' => 'dd8ddf27',
'rsrc/css/phui/phui-form-view.css' => '808329f2',
'rsrc/css/phui/phui-form.css' => '25876baf',
'rsrc/css/phui/phui-header-view.css' => '75aaf372',
'rsrc/css/phui/phui-header-view.css' => '2dd74fe0',
'rsrc/css/phui/phui-icon.css' => 'bc766998',
'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8',
'rsrc/css/phui/phui-info-panel.css' => '27ea50a1',
@ -779,7 +779,7 @@ return array(
'phui-fontkit-css' => 'dd8ddf27',
'phui-form-css' => '25876baf',
'phui-form-view-css' => '808329f2',
'phui-header-view-css' => '75aaf372',
'phui-header-view-css' => '2dd74fe0',
'phui-icon-view-css' => 'bc766998',
'phui-image-mask-css' => '5a8b09c8',
'phui-info-panel-css' => '27ea50a1',

View file

@ -2560,6 +2560,7 @@ phutil_register_library_map(array(
'PhabricatorSpacesCapabilityCreateSpaces' => 'applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php',
'PhabricatorSpacesCapabilityDefaultEdit' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultEdit.php',
'PhabricatorSpacesCapabilityDefaultView' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultView.php',
'PhabricatorSpacesControl' => 'applications/spaces/view/PhabricatorSpacesControl.php',
'PhabricatorSpacesController' => 'applications/spaces/controller/PhabricatorSpacesController.php',
'PhabricatorSpacesDAO' => 'applications/spaces/storage/PhabricatorSpacesDAO.php',
'PhabricatorSpacesEditController' => 'applications/spaces/controller/PhabricatorSpacesEditController.php',
@ -6041,6 +6042,7 @@ phutil_register_library_map(array(
'PhabricatorSpacesCapabilityCreateSpaces' => 'PhabricatorPolicyCapability',
'PhabricatorSpacesCapabilityDefaultEdit' => 'PhabricatorPolicyCapability',
'PhabricatorSpacesCapabilityDefaultView' => 'PhabricatorPolicyCapability',
'PhabricatorSpacesControl' => 'AphrontFormControl',
'PhabricatorSpacesController' => 'PhabricatorController',
'PhabricatorSpacesDAO' => 'PhabricatorLiskDAO',
'PhabricatorSpacesEditController' => 'PhabricatorSpacesController',

View file

@ -57,13 +57,12 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
}
}
$text = null;
$e_text = true;
$errors = array();
$v_space = $paste->getSpacePHID();
if ($is_create && $parent) {
$v_title = pht('Fork of %s', $parent->getFullName());
$v_language = $parent->getLanguage();
$v_text = $parent->getRawContent();
$v_space = $parent->getSpacePHID();
} else {
$v_title = $paste->getTitle();
$v_language = $paste->getLanguage();
@ -81,27 +80,21 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
$v_projects = array_reverse($v_projects);
}
$validation_exception = null;
if ($request->isFormPost()) {
$xactions = array();
$v_text = $request->getStr('text');
if (!strlen($v_text)) {
$e_text = pht('Required');
$errors[] = pht('The paste may not be blank.');
} else {
$e_text = null;
}
$v_title = $request->getStr('title');
$v_language = $request->getStr('language');
$v_view_policy = $request->getStr('can_view');
$v_edit_policy = $request->getStr('can_edit');
$v_projects = $request->getArr('projects');
$v_space = $request->getStr('spacePHID');
// NOTE: The author is the only editor and can always view the paste,
// so it's impossible for them to choose an invalid policy.
if (!$errors) {
if ($is_create || ($v_text !== $paste->getRawContent())) {
$file = PhabricatorPasteEditor::initializeFileForPaste(
$user,
@ -125,6 +118,9 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($v_edit_policy);
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_SPACE)
->setNewValue($v_space);
$proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
$xactions[] = id(new PhabricatorPasteTransaction())
@ -136,13 +132,12 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
->setActor($user)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
try {
$xactions = $editor->applyTransactions($paste, $xactions);
return id(new AphrontRedirectResponse())->setURI($paste->getURI());
} else {
// make sure we update policy so its correctly populated to what
// the user chose
$paste->setViewPolicy($v_view_policy);
$paste->setEditPolicy($v_edit_policy);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
}
}
@ -172,12 +167,19 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
->setObject($paste)
->execute();
$form->appendControl(
id(new PhabricatorSpacesControl())
->setObject($paste)
->setValue($v_space)
->setName('spacePHID'));
$form->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicyObject($paste)
->setPolicies($policies)
->setValue($v_view_policy)
->setName('can_view'));
$form->appendChild(
@ -186,6 +188,7 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicyObject($paste)
->setPolicies($policies)
->setValue($v_edit_policy)
->setName('can_edit'));
$form->appendControl(
@ -199,7 +202,6 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Text'))
->setError($e_text)
->setValue($v_text)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setCustomClass('PhabricatorMonospaced')
@ -222,9 +224,12 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormErrors($errors)
->setForm($form);
if ($validation_exception) {
$form_box->setValidationException($validation_exception);
}
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
if (!$is_create) {
$crumbs->addTextCrumb('P'.$paste->getID(), '/P'.$paste->getID());

View file

@ -42,7 +42,11 @@ final class PhabricatorPaste extends PhabricatorPasteDAO
}
public function getURI() {
return '/P'.$this->getID();
return '/'.$this->getMonogram();
}
public function getMonogram() {
return 'P'.$this->getID();
}
protected function getConfiguration() {

View file

@ -751,6 +751,20 @@ final class PhabricatorUser
$email->getUserPHID());
}
public function getDefaultSpacePHID() {
// TODO: We might let the user switch which space they're "in" later on;
// for now just use the global space if one exists.
$spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($this);
foreach ($spaces as $space) {
if ($space->getIsDefaultNamespace()) {
return $space->getPHID();
}
}
return null;
}
/**
* Grant a user a source of authority, to let them bypass policy checks they

View file

@ -15,7 +15,7 @@ final class PhabricatorSpacesApplication extends PhabricatorApplication {
}
public function getFontIcon() {
return 'fa-compass';
return 'fa-th-large';
}
public function getTitleGlyph() {

View file

@ -28,7 +28,10 @@ final class PhabricatorSpacesNamespacePHIDType
foreach ($handles as $phid => $handle) {
$namespace = $objects[$phid];
$monogram = $namespace->getMonogram();
$handle->setName($namespace->getNamespaceName());
$handle->setURI('/'.$monogram);
}
}

View file

@ -0,0 +1,49 @@
<?php
final class PhabricatorSpacesControl extends AphrontFormControl {
private $object;
protected function shouldRender() {
// Render this control only if some Spaces exist.
return PhabricatorSpacesNamespaceQuery::getAllSpaces();
}
public function setObject(PhabricatorSpacesInterface $object) {
$this->object = $object;
return $this;
}
protected function getCustomControlClass() {
return '';
}
protected function getOptions() {
$viewer = $this->getUser();
$viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer);
$map = mpull($viewer_spaces, 'getNamespaceName', 'getPHID');
asort($map);
return $map;
}
public function renderInput() {
$viewer = $this->getUser();
$this->setLabel(pht('Space'));
$value = $this->getValue();
if ($value === null) {
$value = $viewer->getDefaultSpacePHID();
}
return AphrontFormSelectControl::renderSelectTag(
$value,
$this->getOptions(),
array(
'name' => $this->getName(),
));
}
}

View file

@ -12,6 +12,7 @@ final class PhabricatorTransactions {
const TYPE_BUILDABLE = 'harbormaster:buildable';
const TYPE_TOKEN = 'token:give';
const TYPE_INLINESTATE = 'core:inlinestate';
const TYPE_SPACE = 'core:space';
const COLOR_RED = 'red';
const COLOR_ORANGE = 'orange';

View file

@ -264,6 +264,10 @@ abstract class PhabricatorApplicationTransactionEditor
$types[] = PhabricatorTransactions::TYPE_EDGE;
}
if ($this->object instanceof PhabricatorSpacesInterface) {
$types[] = PhabricatorTransactions::TYPE_SPACE;
}
return $types;
}
@ -292,6 +296,21 @@ abstract class PhabricatorApplicationTransactionEditor
return $object->getEditPolicy();
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return $object->getJoinPolicy();
case PhabricatorTransactions::TYPE_SPACE:
$space_phid = $object->getSpacePHID();
if ($space_phid === null) {
if ($this->getIsNewObject()) {
// In this case, just return `null` so we know this is the initial
// transaction and it should be hidden.
return null;
}
$default_space = PhabricatorSpacesNamespaceQuery::getDefaultSpace();
if ($default_space) {
$space_phid = $default_space->getPHID();
}
}
return $space_phid;
case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $xaction->getMetadataValue('edge:type');
if (!$edge_type) {
@ -337,7 +356,16 @@ abstract class PhabricatorApplicationTransactionEditor
case PhabricatorTransactions::TYPE_BUILDABLE:
case PhabricatorTransactions::TYPE_TOKEN:
case PhabricatorTransactions::TYPE_INLINESTATE:
return $xaction->getNewValue();
case PhabricatorTransactions::TYPE_SPACE:
$space_phid = $xaction->getNewValue();
if (!strlen($space_phid)) {
// If an install has no Spaces, we might end up with the empty string
// here instead of a strict `null`. Just make this work like callers
// might reasonably expect.
return null;
} else {
return $space_phid;
}
case PhabricatorTransactions::TYPE_EDGE:
return $this->getEdgeTransactionNewValue($xaction);
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
@ -437,6 +465,7 @@ abstract class PhabricatorApplicationTransactionEditor
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
case PhabricatorTransactions::TYPE_INLINESTATE:
case PhabricatorTransactions::TYPE_EDGE:
case PhabricatorTransactions::TYPE_SPACE:
case PhabricatorTransactions::TYPE_COMMENT:
return $this->applyBuiltinInternalTransaction($object, $xaction);
}
@ -485,6 +514,7 @@ abstract class PhabricatorApplicationTransactionEditor
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_INLINESTATE:
case PhabricatorTransactions::TYPE_SPACE:
case PhabricatorTransactions::TYPE_COMMENT:
return $this->applyBuiltinExternalTransaction($object, $xaction);
}
@ -537,6 +567,9 @@ abstract class PhabricatorApplicationTransactionEditor
case PhabricatorTransactions::TYPE_JOIN_POLICY:
$object->setJoinPolicy($xaction->getNewValue());
break;
case PhabricatorTransactions::TYPE_SPACE:
$object->setSpacePHID($xaction->getNewValue());
break;
}
}
@ -1190,18 +1223,9 @@ abstract class PhabricatorApplicationTransactionEditor
PhabricatorPolicyCapability::CAN_VIEW);
break;
case PhabricatorTransactions::TYPE_VIEW_POLICY:
PhabricatorPolicyFilter::requireCapability(
$actor,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
break;
case PhabricatorTransactions::TYPE_EDIT_POLICY:
PhabricatorPolicyFilter::requireCapability(
$actor,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
break;
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_SPACE:
PhabricatorPolicyFilter::requireCapability(
$actor,
$object,
@ -1882,6 +1906,12 @@ abstract class PhabricatorApplicationTransactionEditor
$type,
PhabricatorPolicyCapability::CAN_EDIT);
break;
case PhabricatorTransactions::TYPE_SPACE:
$errors[] = $this->validateSpaceTransactions(
$object,
$xactions,
$type);
break;
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$groups = array();
foreach ($xactions as $xaction) {
@ -1968,6 +1998,52 @@ abstract class PhabricatorApplicationTransactionEditor
return $errors;
}
private function validateSpaceTransactions(
PhabricatorLiskDAO $object,
array $xactions,
$transaction_type) {
$errors = array();
$all_spaces = PhabricatorSpacesNamespaceQuery::getAllSpaces();
$viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces(
$this->getActor());
foreach ($xactions as $xaction) {
$space_phid = $xaction->getNewValue();
if ($space_phid === null) {
if (!$all_spaces) {
// The install doesn't have any spaces, so this is fine.
continue;
}
// The install has some spaces, so every object needs to be put
// in a valid space.
$errors[] = new PhabricatorApplicationTransactionValidationError(
$transaction_type,
pht('Invalid'),
pht('You must choose a space for this object.'),
$xaction);
continue;
}
// If the PHID isn't `null`, it needs to be a valid space that the
// viewer can see.
if (empty($viewer_spaces[$space_phid])) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$transaction_type,
pht('Invalid'),
pht(
'You can not shift this object in the selected space, because '.
'the space does not exist or you do not have access to it.'),
$xaction);
}
}
return $errors;
}
protected function adjustObjectForPolicyChecks(
PhabricatorLiskDAO $object,
array $xactions) {

View file

@ -250,6 +250,14 @@ abstract class PhabricatorApplicationTransaction
$phids[] = array($new);
}
break;
case PhabricatorTransactions::TYPE_SPACE:
if ($old) {
$phids[] = array($old);
}
if ($new) {
$phids[] = array($new);
}
break;
case PhabricatorTransactions::TYPE_TOKEN:
break;
case PhabricatorTransactions::TYPE_BUILDABLE:
@ -369,6 +377,8 @@ abstract class PhabricatorApplicationTransaction
return 'fa-wrench';
case PhabricatorTransactions::TYPE_TOKEN:
return 'fa-trophy';
case PhabricatorTransactions::TYPE_SPACE:
return 'fa-th-large';
}
return 'fa-pencil';
@ -438,6 +448,7 @@ abstract class PhabricatorApplicationTransaction
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_SPACE:
if ($this->getOldValue() === null) {
return true;
} else {
@ -597,6 +608,8 @@ abstract class PhabricatorApplicationTransaction
return pht(
'All users are already subscribed to this %s.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_SPACE:
return pht('This object is already in that space.');
case PhabricatorTransactions::TYPE_EDGE:
return pht('Edges already exist; transaction has no effect.');
}
@ -636,6 +649,12 @@ abstract class PhabricatorApplicationTransaction
$this->getApplicationObjectTypeName(),
$this->renderPolicyName($old, 'old'),
$this->renderPolicyName($new, 'new'));
case PhabricatorTransactions::TYPE_SPACE:
return pht(
'%s shifted this object from the %s space to the %s space.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($old),
$this->renderHandleLink($new));
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
@ -821,6 +840,13 @@ abstract class PhabricatorApplicationTransaction
'%s updated subscribers of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_SPACE:
return pht(
'%s shifted %s from the %s space to the %s space.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$this->renderHandleLink($old),
$this->renderHandleLink($new));
case PhabricatorTransactions::TYPE_EDGE:
$new = ipull($new, 'dst');
$old = ipull($old, 'dst');

View file

@ -174,6 +174,20 @@ final class PHUIHeaderView extends AphrontTagView {
$header = array();
$header[] = $this->renderObjectSpaceInformation();
if ($this->objectName) {
$header[] = array(
phutil_tag(
'a',
array(
'href' => '/'.$this->objectName,
),
$this->objectName),
' ',
);
}
if ($this->actionLinks) {
$actions = array();
foreach ($this->actionLinks as $button) {
@ -200,18 +214,6 @@ final class PHUIHeaderView extends AphrontTagView {
}
$header[] = $this->header;
if ($this->objectName) {
array_unshift(
$header,
phutil_tag(
'a',
array(
'href' => '/'.$this->objectName,
),
$this->objectName),
' ');
}
if ($this->tags) {
$header[] = ' ';
$header[] = phutil_tag(
@ -268,9 +270,9 @@ final class PHUIHeaderView extends AphrontTagView {
}
private function renderPolicyProperty(PhabricatorPolicyInterface $object) {
$policies = PhabricatorPolicyQuery::loadPolicies(
$this->getUser(),
$object);
$viewer = $this->getUser();
$policies = PhabricatorPolicyQuery::loadPolicies($viewer, $object);
$view_capability = PhabricatorPolicyCapability::CAN_VIEW;
$policy = idx($policies, $view_capability);
@ -294,4 +296,41 @@ final class PHUIHeaderView extends AphrontTagView {
return array($icon, $link);
}
private function renderObjectSpaceInformation() {
$viewer = $this->getUser();
$object = $this->policyObject;
if (!$object) {
return;
}
if (!($object instanceof PhabricatorSpacesInterface)) {
return;
}
$space_phid = $object->getSpacePHID();
if ($space_phid === null) {
$default_space = PhabricatorSpacesNamespaceQuery::getDefaultSpace();
if ($default_space) {
$space_phid = $default_space->getPHID();
}
}
if ($space_phid === null) {
return;
}
return phutil_tag(
'span',
array(
'class' => 'spaces-name',
),
array(
$viewer->renderHandle($space_phid),
' | ',
));
}
}

View file

@ -140,3 +140,11 @@ body.device-phone .phui-header-view {
.device .phui-header-action-links .phui-mobile-menu {
display: inline-block;
}
.spaces-name {
color: {$lightbluetext};
}
.spaces-name .phui-handle {
color: #000;
}