1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-27 15:08:20 +01:00

Explain policy exception rules to users

Summary:
Ref T603. Adds clarifying text which expands on policies and explains exceptions and rules. The goal is to provide an easy way for users to learn about special policy rules, like "task owners can always see a task".

This presentation might be a little aggressive. That's probably OK as we introduce policies, but something a little more tempered might be better down the road.

Test Plan: See screenshot.

Reviewers: btrahan, chad

Reviewed By: chad

CC: aran

Maniphest Tasks: T603

Differential Revision: https://secure.phabricator.com/D7150
This commit is contained in:
epriestley 2013-09-27 08:43:41 -07:00
parent e0f99484ac
commit 2e5ac128b3
63 changed files with 565 additions and 56 deletions

View file

@ -836,7 +836,7 @@ celerity_register_resource_map(array(
),
'aphront-dialog-view-css' =>
array(
'uri' => '/res/337bd2a9/rsrc/css/aphront/dialog-view.css',
'uri' => '/res/609ccc78/rsrc/css/aphront/dialog-view.css',
'type' => 'css',
'requires' =>
array(
@ -1176,7 +1176,7 @@ celerity_register_resource_map(array(
),
'herald-rule-editor' =>
array(
'uri' => '/res/f8ee0e9c/rsrc/js/application/herald/HeraldRuleEditor.js',
'uri' => '/res/36222dde/rsrc/js/application/herald/HeraldRuleEditor.js',
'type' => 'js',
'requires' =>
array(
@ -4162,7 +4162,7 @@ celerity_register_resource_map(array(
), array(
'packages' =>
array(
'15affac5' =>
'de5898ae' =>
array(
'name' => 'core.pkg.css',
'symbols' =>
@ -4209,7 +4209,7 @@ celerity_register_resource_map(array(
39 => 'phabricator-property-list-view-css',
40 => 'phabricator-tag-view-css',
),
'uri' => '/res/pkg/15affac5/core.pkg.css',
'uri' => '/res/pkg/de5898ae/core.pkg.css',
'type' => 'css',
),
'8977e356' =>
@ -4399,15 +4399,15 @@ celerity_register_resource_map(array(
),
'reverse' =>
array(
'aphront-dialog-view-css' => '15affac5',
'aphront-error-view-css' => '15affac5',
'aphront-list-filter-view-css' => '15affac5',
'aphront-pager-view-css' => '15affac5',
'aphront-panel-view-css' => '15affac5',
'aphront-table-view-css' => '15affac5',
'aphront-tokenizer-control-css' => '15affac5',
'aphront-tooltip-css' => '15affac5',
'aphront-typeahead-control-css' => '15affac5',
'aphront-dialog-view-css' => 'de5898ae',
'aphront-error-view-css' => 'de5898ae',
'aphront-list-filter-view-css' => 'de5898ae',
'aphront-pager-view-css' => 'de5898ae',
'aphront-panel-view-css' => 'de5898ae',
'aphront-table-view-css' => 'de5898ae',
'aphront-tokenizer-control-css' => 'de5898ae',
'aphront-tooltip-css' => 'de5898ae',
'aphront-typeahead-control-css' => 'de5898ae',
'differential-changeset-view-css' => '44bfe40c',
'differential-core-view-css' => '44bfe40c',
'differential-inline-comment-editor' => '5e9e5c4e',
@ -4421,7 +4421,7 @@ celerity_register_resource_map(array(
'differential-table-of-contents-css' => '44bfe40c',
'diffusion-commit-view-css' => 'c8ce2d88',
'diffusion-icons-css' => 'c8ce2d88',
'global-drag-and-drop-css' => '15affac5',
'global-drag-and-drop-css' => 'de5898ae',
'inline-comment-summary-css' => '44bfe40c',
'javelin-aphlict' => '8977e356',
'javelin-behavior' => '9564fa17',
@ -4494,54 +4494,54 @@ celerity_register_resource_map(array(
'javelin-util' => '9564fa17',
'javelin-vector' => '9564fa17',
'javelin-workflow' => '9564fa17',
'lightbox-attachment-css' => '15affac5',
'lightbox-attachment-css' => 'de5898ae',
'maniphest-task-summary-css' => '49898640',
'phabricator-action-list-view-css' => '15affac5',
'phabricator-application-launch-view-css' => '15affac5',
'phabricator-action-list-view-css' => 'de5898ae',
'phabricator-application-launch-view-css' => 'de5898ae',
'phabricator-busy' => '8977e356',
'phabricator-content-source-view-css' => '44bfe40c',
'phabricator-core-css' => '15affac5',
'phabricator-crumbs-view-css' => '15affac5',
'phabricator-core-css' => 'de5898ae',
'phabricator-crumbs-view-css' => 'de5898ae',
'phabricator-drag-and-drop-file-upload' => '5e9e5c4e',
'phabricator-dropdown-menu' => '8977e356',
'phabricator-file-upload' => '8977e356',
'phabricator-filetree-view-css' => '15affac5',
'phabricator-flag-css' => '15affac5',
'phabricator-filetree-view-css' => 'de5898ae',
'phabricator-flag-css' => 'de5898ae',
'phabricator-hovercard' => '8977e356',
'phabricator-jump-nav' => '15affac5',
'phabricator-jump-nav' => 'de5898ae',
'phabricator-keyboard-shortcut' => '8977e356',
'phabricator-keyboard-shortcut-manager' => '8977e356',
'phabricator-main-menu-view' => '15affac5',
'phabricator-main-menu-view' => 'de5898ae',
'phabricator-menu-item' => '8977e356',
'phabricator-nav-view-css' => '15affac5',
'phabricator-nav-view-css' => 'de5898ae',
'phabricator-notification' => '8977e356',
'phabricator-notification-css' => '15affac5',
'phabricator-notification-menu-css' => '15affac5',
'phabricator-notification-css' => 'de5898ae',
'phabricator-notification-menu-css' => 'de5898ae',
'phabricator-object-selector-css' => '44bfe40c',
'phabricator-phtize' => '8977e356',
'phabricator-prefab' => '8977e356',
'phabricator-project-tag-css' => '49898640',
'phabricator-property-list-view-css' => '15affac5',
'phabricator-remarkup-css' => '15affac5',
'phabricator-property-list-view-css' => 'de5898ae',
'phabricator-remarkup-css' => 'de5898ae',
'phabricator-shaped-request' => '5e9e5c4e',
'phabricator-side-menu-view-css' => '15affac5',
'phabricator-standard-page-view' => '15affac5',
'phabricator-tag-view-css' => '15affac5',
'phabricator-side-menu-view-css' => 'de5898ae',
'phabricator-standard-page-view' => 'de5898ae',
'phabricator-tag-view-css' => 'de5898ae',
'phabricator-textareautils' => '8977e356',
'phabricator-tooltip' => '8977e356',
'phabricator-transaction-view-css' => '15affac5',
'phabricator-zindex-css' => '15affac5',
'phui-button-css' => '15affac5',
'phui-form-css' => '15affac5',
'phui-form-view-css' => '15affac5',
'phui-header-view-css' => '15affac5',
'phui-icon-view-css' => '15affac5',
'phui-object-item-list-view-css' => '15affac5',
'phui-spacing-css' => '15affac5',
'sprite-apps-large-css' => '15affac5',
'sprite-gradient-css' => '15affac5',
'sprite-icons-css' => '15affac5',
'sprite-menu-css' => '15affac5',
'syntax-highlighting-css' => '15affac5',
'phabricator-transaction-view-css' => 'de5898ae',
'phabricator-zindex-css' => 'de5898ae',
'phui-button-css' => 'de5898ae',
'phui-form-css' => 'de5898ae',
'phui-form-view-css' => 'de5898ae',
'phui-header-view-css' => 'de5898ae',
'phui-icon-view-css' => 'de5898ae',
'phui-object-item-list-view-css' => 'de5898ae',
'phui-spacing-css' => 'de5898ae',
'sprite-apps-large-css' => 'de5898ae',
'sprite-gradient-css' => 'de5898ae',
'sprite-icons-css' => 'de5898ae',
'sprite-menu-css' => 'de5898ae',
'syntax-highlighting-css' => 'de5898ae',
),
));

View file

@ -846,6 +846,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationPhortune' => 'applications/phortune/application/PhabricatorApplicationPhortune.php',
'PhabricatorApplicationPhrequent' => 'applications/phrequent/application/PhabricatorApplicationPhrequent.php',
'PhabricatorApplicationPhriction' => 'applications/phriction/application/PhabricatorApplicationPhriction.php',
'PhabricatorApplicationPolicy' => 'applications/policy/application/PhabricatorApplicationPolicy.php',
'PhabricatorApplicationPonder' => 'applications/ponder/application/PhabricatorApplicationPonder.php',
'PhabricatorApplicationProject' => 'applications/project/application/PhabricatorApplicationProject.php',
'PhabricatorApplicationReleeph' => 'applications/releeph/application/PhabricatorApplicationReleeph.php',
@ -1459,8 +1460,10 @@ phutil_register_library_map(array(
'PhabricatorPolicyCapability' => 'applications/policy/constants/PhabricatorPolicyCapability.php',
'PhabricatorPolicyConfigOptions' => 'applications/config/option/PhabricatorPolicyConfigOptions.php',
'PhabricatorPolicyConstants' => 'applications/policy/constants/PhabricatorPolicyConstants.php',
'PhabricatorPolicyController' => 'applications/policy/controller/PhabricatorPolicyController.php',
'PhabricatorPolicyDataTestCase' => 'applications/policy/__tests__/PhabricatorPolicyDataTestCase.php',
'PhabricatorPolicyException' => 'applications/policy/exception/PhabricatorPolicyException.php',
'PhabricatorPolicyExplainController' => 'applications/policy/controller/PhabricatorPolicyExplainController.php',
'PhabricatorPolicyFilter' => 'applications/policy/filter/PhabricatorPolicyFilter.php',
'PhabricatorPolicyInterface' => 'applications/policy/interface/PhabricatorPolicyInterface.php',
'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php',
@ -2941,6 +2944,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationPhortune' => 'PhabricatorApplication',
'PhabricatorApplicationPhrequent' => 'PhabricatorApplication',
'PhabricatorApplicationPhriction' => 'PhabricatorApplication',
'PhabricatorApplicationPolicy' => 'PhabricatorApplication',
'PhabricatorApplicationPonder' => 'PhabricatorApplication',
'PhabricatorApplicationProject' => 'PhabricatorApplication',
'PhabricatorApplicationReleeph' => 'PhabricatorApplication',
@ -3612,8 +3616,10 @@ phutil_register_library_map(array(
'PhabricatorPolicyAwareTestQuery' => 'PhabricatorPolicyAwareQuery',
'PhabricatorPolicyCapability' => 'PhabricatorPolicyConstants',
'PhabricatorPolicyConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPolicyController' => 'PhabricatorController',
'PhabricatorPolicyDataTestCase' => 'PhabricatorTestCase',
'PhabricatorPolicyException' => 'Exception',
'PhabricatorPolicyExplainController' => 'PhabricatorPolicyController',
'PhabricatorPolicyQuery' => 'PhabricatorQuery',
'PhabricatorPolicyTestCase' => 'PhabricatorTestCase',
'PhabricatorPolicyTestObject' => 'PhabricatorPolicyInterface',

View file

@ -81,4 +81,8 @@ final class PhabricatorAuthProviderConfig extends PhabricatorAuthDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -32,5 +32,9 @@ final class PhabricatorChatLogChannel
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -28,6 +28,10 @@ final class PhabricatorChatLogEvent
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,

View file

@ -183,4 +183,8 @@ abstract class ConduitAPIMethod
return true;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -30,4 +30,8 @@ final class PhabricatorConduitMethodCallLog extends PhabricatorConduitDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -59,4 +59,8 @@ final class PhabricatorConfigEntry extends PhabricatorConfigEntryDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -189,4 +189,8 @@ final class ConpherenceThread extends ConpherenceDAO
return isset($participants[$user->getPHID()]);
}
public function describeAutomaticCapability($capability) {
return pht("Participants in a thread can always view and edit it.");
}
}

View file

@ -53,4 +53,8 @@ final class PhabricatorCountdown
return ($viewer->getPHID() == $this->getAuthorPHID());
}
public function describeAutomaticCapability($capability) {
return pht('The author of a countdown can always view and edit it.');
}
}

View file

@ -41,4 +41,8 @@ final class PhabricatorDaemonLog extends PhabricatorDaemonDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -320,4 +320,12 @@ final class DifferentialDiff
return false;
}
public function describeAutomaticCapability($capability) {
if ($this->getRevision()) {
return pht(
'This diff is attached to a revision, and inherits its policies.');
}
return null;
}
}

View file

@ -329,6 +329,26 @@ final class DifferentialRevision extends DifferentialDAO
return false;
}
public function describeAutomaticCapability($capability) {
$description = array(
pht('The owner of a revision can always view and edit it.'),
);
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
$description[] = pht(
"A revision's reviewers can always view it.");
if ($this->getRepository()) {
$description[] = pht(
'This revision belongs to a repository. Other users must be able '.
'to view the repository in order to view this revision.');
}
break;
}
return $description;
}
public function getUsersToNotifyOfTokenGiven() {
return array(
$this->getAuthorPHID(),

View file

@ -61,4 +61,8 @@ final class DivinerLiveBook extends DivinerDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -143,6 +143,10 @@ final class DivinerLiveSymbol extends DivinerDAO
return $this->getBook()->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return pht('Atoms inherit the policies of the books they are part of.');
}
/* -( Markup Interface )--------------------------------------------------- */

View file

@ -77,4 +77,8 @@ final class DoorkeeperExternalObject extends DoorkeeperDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -324,4 +324,8 @@ abstract class PhabricatorFeedStory implements PhabricatorPolicyInterface {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -830,6 +830,10 @@ final class PhabricatorFile extends PhabricatorFileDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
/* -( PhabricatorSubscribableInterface Implementation )-------------------- */

View file

@ -50,4 +50,8 @@ final class PhabricatorFlag extends PhabricatorFlagDAO
return ($viewer->getPHID() == $this->getOwnerPHID());
}
public function describeAutomaticCapability($capability) {
return pht('Flags are private. Only you can view or edit your flags.');
}
}

View file

@ -202,4 +202,10 @@ final class HeraldRule extends HeraldDAO
}
}
public function describeAutomaticCapability($capability) {
// TODO: (T603) Sort this out.
return null;
}
}

View file

@ -96,6 +96,12 @@ final class LegalpadDocument extends LegalpadDAO
return ($user->getPHID() == $this->getCreatorPHID());
}
public function describeAutomaticCapability($capability) {
return pht(
'The author of a document can always view and edit it.');
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {

View file

@ -61,5 +61,9 @@ final class PhabricatorFileImageMacro extends PhabricatorFileDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -37,4 +37,8 @@ final class PhabricatorMetaMTAMailingList extends PhabricatorMetaMTADAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -221,6 +221,11 @@ final class ManiphestTask extends ManiphestDAO
return false;
}
public function describeAutomaticCapability($capability) {
return pht(
'The owner of a task can always view and edit it.');
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */

View file

@ -30,6 +30,10 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
public function getConfiguration() {
return array(
// This information is better available from the history table.

View file

@ -61,6 +61,11 @@ final class PhabricatorPaste extends PhabricatorPasteDAO
return ($user->getPHID() == $this->getAuthorPHID());
}
public function describeAutomaticCapability($capability) {
return pht(
'The author of a paste can always view and edit it.');
}
public function getFullName() {
$title = $this->getTitle();
if (!$title) {

View file

@ -102,4 +102,9 @@ final class PhabricatorExternalAccount extends PhabricatorUserDAO
return ($viewer->getPHID() == $this->getUserPHID());
}
public function describeAutomaticCapability($capability) {
// TODO: (T603) This is complicated.
return null;
}
}

View file

@ -834,6 +834,15 @@ EOBODY;
return $this->getPHID() && ($viewer->getPHID() === $this->getPHID());
}
public function describeAutomaticCapability($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_EDIT:
return pht('Only you can edit your information.');
default:
return null;
}
}
/* -( PhabricatorCustomFieldInterface )------------------------------------ */

View file

@ -188,6 +188,20 @@ final class PhameBlog extends PhameDAO
}
public function describeAutomaticCapability($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return pht(
'Users who can edit or post on a blog can always view it.');
case PhabricatorPolicyCapability::CAN_JOIN:
return pht(
'Users who can edit a blog can always post on it.');
}
return null;
}
/* -( PhabricatorMarkupInterface Implementation )-------------------------- */

View file

@ -150,6 +150,12 @@ final class PhamePost extends PhameDAO
}
public function describeAutomaticCapability($capability) {
return pht(
'The author of a blog post can always view and edit it.');
}
/* -( PhabricatorMarkupInterface Implementation )-------------------------- */

View file

@ -236,4 +236,8 @@ final class PhabricatorObjectHandle
return true;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -45,4 +45,8 @@ final class PhluxVariable extends PhluxDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -104,4 +104,8 @@ final class PholioImage extends PholioDAO
return $this->getMock()->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -149,6 +149,10 @@ final class PholioMock extends PholioDAO
return ($viewer->getPHID() == $this->getAuthorPHID());
}
public function describeAutomaticCapability($capability) {
return pht("A mock's owner can always view and edit it.");
}
/* -( PhabricatorMarkupInterface )----------------------------------------- */

View file

@ -46,7 +46,7 @@ final class PhortuneAccount extends PhortuneDAO
}
public function getPolicy($capability) {
return false;
return PhabricatorPolicies::POLICY_NOONE;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
@ -54,4 +54,9 @@ final class PhortuneAccount extends PhortuneDAO
return isset($members[$viewer->getPHID()]);
}
public function describeAutomaticCapability($capability) {
return pht('Members of an account can always view and edit it.');
}
}

View file

@ -107,4 +107,9 @@ final class PhortunePaymentMethod extends PhortuneDAO
$viewer);
}
public function describeAutomaticCapability($capability) {
return pht(
'Members of an account can always view and edit its payment methods.');
}
}

View file

@ -73,4 +73,8 @@ final class PhortuneProduct extends PhortuneDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -126,6 +126,14 @@ final class PhrictionDocument extends PhrictionDAO
return false;
}
public function describeAutomaticCapability($capability) {
if ($this->hasProject()) {
return pht(
"This is a project wiki page, and inherits the project's policies.");
}
return null;
}
public function isAutomaticallySubscribed($phid) {
return false;
}

View file

@ -38,4 +38,8 @@ final class PhabricatorPolicyTestObject
return $this;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -0,0 +1,23 @@
<?php
final class PhabricatorApplicationPolicy extends PhabricatorApplication {
public function shouldAppearInLaunchView() {
return false;
}
public function canUninstall() {
return false;
}
public function getRoutes() {
return array(
'/policy/' => array(
'explain/(?P<phid>[^/]+)/(?P<capability>[^/]+)/'
=> 'PhabricatorPolicyExplainController',
),
);
}
}

View file

@ -0,0 +1,5 @@
<?php
abstract class PhabricatorPolicyController extends PhabricatorController {
}

View file

@ -0,0 +1,73 @@
<?php
final class PhabricatorPolicyExplainController
extends PhabricatorPolicyController {
private $phid;
private $capability;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->phid = $data['phid'];
$this->capability = $data['capability'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$phid = $this->phid;
$capability = $this->capability;
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($phid))
->executeOne();
if (!$object) {
return new Aphront404Response();
}
$policies = PhabricatorPolicyQuery::loadPolicies(
$viewer,
$object);
$policy = idx($policies, $capability);
if (!$policy) {
return new Aphront404Response();
}
$handle = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs(array($phid))
->executeOne();
$object_uri = $handle->getURI();
$explanation = $policy->getExplanation($capability);
$auto_info = (array)$object->describeAutomaticCapability($capability);
foreach ($auto_info as $key => $info) {
$auto_info[$key] = phutil_tag('li', array(), $info);
}
if ($auto_info) {
$auto_info = phutil_tag('ul', array(), $auto_info);
}
$content = array(
$explanation,
$auto_info,
);
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setClass('aphront-access-dialog')
->setTitle(pht('Policy Details'))
->appendChild($content)
->addCancelButton($object_uri, pht('Done'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View file

@ -130,6 +130,35 @@ final class PhabricatorPolicy {
return $this->getName();
}
public function getExplanation($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
switch ($this->getPHID()) {
case PhabricatorPolicies::POLICY_PUBLIC:
return pht('Visible to the entire internet.');
case PhabricatorPolicies::POLICY_USER:
return pht('Visible to all logged in users.');
case PhabricatorPolicies::POLICY_ADMIN:
return pht('Visible to all administrators.');
case PhabricatorPolicies::POLICY_NOONE:
return pht('Not visible to anyone by default.');
}
switch ($this->getType()) {
case PhabricatorPolicyType::TYPE_PROJECT:
return pht(
'Visible to members of the project "%s".',
$this->getName());
case PhabricatorPolicyType::TYPE_MASKED:
return pht('Other: %s', $this->getName());
}
break;
}
return pht('?');
}
public function getFullName() {
switch ($this->getType()) {
case PhabricatorPolicyType::TYPE_PROJECT:

View file

@ -6,4 +6,32 @@ interface PhabricatorPolicyInterface {
public function getPolicy($capability);
public function hasAutomaticCapability($capability, PhabricatorUser $viewer);
/**
* Describe exceptions to an object's policy setting.
*
* The intent of this method is to explain and inform users about special
* cases which override configured policy settings. If this object has any
* such exceptions, explain them by returning one or more human-readable
* strings which describe the exception in a broad, categorical way. For
* example:
*
* - "The owner of an X can always view and edit it."
* - "Members of a Y can always view it."
*
* You can return `null`, a single string, or a list of strings.
*
* The relevant capability to explain (like "view") is passed as a parameter.
* You should tailor any messages to be relevant to that capability, although
* they do not need to exclusively describe the capability, and in some cases
* being more general ("The author can view and edit...") will be more clear.
*
* Messages should describe general rules, not specific objects, because the
* main goal is to teach the user the rules. For example, write "the author",
* not the specific author's name.
*
* @param const @{class:PhabricatorPolicyCapability} constant.
* @return wild Description of policy exceptions. See above.
*/
public function describeAutomaticCapability($capability);
}

View file

@ -15,10 +15,9 @@ final class PhabricatorPolicyQuery extends PhabricatorQuery {
return $this;
}
public static function renderPolicyDescriptions(
public static function loadPolicies(
PhabricatorUser $viewer,
PhabricatorPolicyInterface $object,
$icon=false) {
PhabricatorPolicyInterface $object) {
$results = array();
$policies = null;
@ -32,7 +31,7 @@ final class PhabricatorPolicyQuery extends PhabricatorQuery {
}
if (isset($global[$policy])) {
$results[$capability] = $global[$policy]->renderDescription($icon);
$results[$capability] = $global[$policy];
continue;
}
@ -45,12 +44,26 @@ final class PhabricatorPolicyQuery extends PhabricatorQuery {
->execute();
}
$results[$capability] = $policies[$policy]->renderDescription($icon);
$results[$capability] = $policies[$policy];
}
return $results;
}
public static function renderPolicyDescriptions(
PhabricatorUser $viewer,
PhabricatorPolicyInterface $object,
$icon = false) {
$policies = self::loadPolicies($viewer, $object);
foreach ($policies as $capability => $policy) {
$policies[$capability] = $policy->renderDescription($icon);
}
return $policies;
}
public function execute() {
if (!$this->viewer) {
throw new Exception('Call setViewer() before execute()!');

View file

@ -145,6 +145,9 @@ final class PonderAnswer extends PonderDAO
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
if ($this->getAuthorPHID() == $viewer->getPHID()) {
return true;
}
return $this->getQuestion()->hasAutomaticCapability(
$capability,
$viewer);
@ -154,6 +157,19 @@ final class PonderAnswer extends PonderDAO
}
public function describeAutomaticCapability($capability) {
$out = array();
$out[] = pht("The author of an answer can always view and edit it.");
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
$out[] = pht(
"The user who asks a question can always view the answers.");
break;
}
return $out;
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */

View file

@ -203,6 +203,12 @@ final class PonderQuestion extends PonderDAO
return ($viewer->getPHID() == $this->getAuthorPHID());
}
public function describeAutomaticCapability($capability) {
return pht(
'The user who asked a question can always view and edit it.');
}
public function getOriginalTitle() {
// TODO: Make this actually save/return the original title.
return $this->getTitle();

View file

@ -60,6 +60,16 @@ final class PhabricatorProject extends PhabricatorProjectDAO
return false;
}
public function describeAutomaticCapability($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return pht("Members of a project can always view it.");
case PhabricatorPolicyCapability::CAN_JOIN:
return pht("Users who can edit a project can always join it.");
}
return null;
}
public function isUserMember($user_phid) {
return $this->assertAttachedKey($this->sparseMembers, $user_phid);
}

View file

@ -189,4 +189,9 @@ final class ReleephBranch extends ReleephDAO
return $this->getProject()->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -193,4 +193,9 @@ final class ReleephProject extends ReleephDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -306,6 +306,11 @@ final class ReleephRequest extends ReleephDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
/* -( PhabricatorCustomFieldInterface )------------------------------------ */

View file

@ -701,6 +701,11 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
/* -( PhabricatorMarkupInterface )----------------------------------------- */

View file

@ -87,4 +87,8 @@ final class PhabricatorRepositoryArcanistProject
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -167,6 +167,12 @@ final class PhabricatorRepositoryCommit
return $this->getRepository()->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return pht(
'Commits inherit the policies of the repository they belong to.');
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {

View file

@ -39,4 +39,11 @@ final class PhabricatorNamedQuery extends PhabricatorSearchDAO
return false;
}
public function describeAutomaticCapability($capability) {
return pht(
'The queries you have saved are private. Only you can view or edit '.
'them.');
}
}

View file

@ -63,4 +63,8 @@ final class PhabricatorSavedQuery extends PhabricatorSearchDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -96,6 +96,12 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO
return ($viewer->getPHID() == $this->getAuthorPHID());
}
public function describeAutomaticCapability($capability) {
return pht(
'The author of a poll can always view and edit it.');
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */

View file

@ -27,6 +27,10 @@ final class PhabricatorToken extends PhabricatorTokenDAO
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
public function renderIcon() {
// TODO: Maybe move to a View class?

View file

@ -48,4 +48,17 @@ final class PhabricatorTokenGiven extends PhabricatorTokenDAO
}
}
public function describeAutomaticCapability($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return pht(
'A token inherits the policies of the object it is awarded to.');
case PhabricatorPolicyCapability::CAN_EDIT:
return pht(
'The user who gave a token can always edit it.');
}
return null;
}
}

View file

@ -537,4 +537,10 @@ abstract class PhabricatorApplicationTransaction
return ($viewer->getPHID() == $this->getAuthorPHID());
}
public function describeAutomaticCapability($capability) {
// TODO: (T603) Exact policies are unclear here.
return null;
}
}

View file

@ -113,4 +113,9 @@ abstract class PhabricatorApplicationTransactionComment
return ($viewer->getPHID() == $this->getAuthorPHID());
}
public function describeAutomaticCapability($capability) {
// TODO: (T603) Policies are murky.
return null;
}
}

View file

@ -148,11 +148,7 @@ final class PHUIHeaderView extends AphrontView {
}
if ($this->policyObject) {
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$this->getUser(),
$this->policyObject,
true);
$property_list[] = $descriptions[PhabricatorPolicyCapability::CAN_VIEW];
$property_list[] = $this->renderPolicyProperty($this->policyObject);
}
$header[] = phutil_tag(
@ -179,5 +175,31 @@ final class PHUIHeaderView extends AphrontView {
));
}
private function renderPolicyProperty(PhabricatorPolicyInterface $object) {
$policies = PhabricatorPolicyQuery::loadPolicies(
$this->getUser(),
$object);
$view_capability = PhabricatorPolicyCapability::CAN_VIEW;
$policy = idx($policies, $view_capability);
if (!$policy) {
return null;
}
$phid = $object->getPHID();
$icon = id(new PHUIIconView())
->setSpriteSheet(PHUIIconView::SPRITE_STATUS)
->setSpriteIcon($policy->getIcon());
$link = javelin_tag(
'a',
array(
'href' => '/policy/explain/'.$phid.'/'.$view_capability.'/',
'sigil' => 'workflow',
),
$policy->getName());
return array($icon, $link);
}
}

View file

@ -111,3 +111,8 @@
.aphront-access-dialog {
width: 50%;
}
.aphront-access-dialog ul {
margin: 12px 24px;
list-style: circle;
}