Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15395030
D17453.id41964.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Referenced Files
None
Subscribers
None
D17453.id41964.diff
View Options
diff --git a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
--- a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
+++ b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
@@ -210,6 +210,8 @@
- `claim` //Optional bool.// By default, closing an unassigned task claims
it. You can set this to `false` to disable this behavior for a particular
status.
+ - `locked` //Optional bool.// Lock tasks in this status, preventing users
+ from commenting.
Statuses will appear in the UI in the order specified. Note the status marked
`special` as `duplicate` is not settable directly and will not appear in UI
diff --git a/src/applications/maniphest/constants/ManiphestTaskStatus.php b/src/applications/maniphest/constants/ManiphestTaskStatus.php
--- a/src/applications/maniphest/constants/ManiphestTaskStatus.php
+++ b/src/applications/maniphest/constants/ManiphestTaskStatus.php
@@ -156,6 +156,10 @@
return !self::isOpenStatus($status);
}
+ public static function isLockedStatus($status) {
+ return self::getStatusAttribute($status, 'locked', false);
+ }
+
public static function getStatusActionName($status) {
return self::getStatusAttribute($status, 'name.action');
}
@@ -277,6 +281,7 @@
'keywords' => 'optional list<string>',
'disabled' => 'optional bool',
'claim' => 'optional bool',
+ 'locked' => 'optional bool',
));
}
diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
--- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
@@ -257,6 +257,12 @@
$task,
PhabricatorPolicyCapability::CAN_EDIT);
+ $can_interact = PhabricatorPolicyFilter::canInteract($viewer, $task);
+
+ // We expect a policy dialog if you can't edit the task, and expect a
+ // lock override dialog if you can't interact with it.
+ $workflow_edit = (!$can_edit || !$can_interact);
+
$curtain = $this->newCurtainView($task);
$curtain->addAction(
@@ -265,7 +271,7 @@
->setIcon('fa-pencil')
->setHref($this->getApplicationURI("/task/edit/{$id}/"))
->setDisabled(!$can_edit)
- ->setWorkflow(!$can_edit));
+ ->setWorkflow($workflow_edit));
$edit_config = $edit_engine->loadDefaultEditConfiguration($task);
$can_create = (bool)$edit_config;
diff --git a/src/applications/maniphest/storage/ManiphestTask.php b/src/applications/maniphest/storage/ManiphestTask.php
--- a/src/applications/maniphest/storage/ManiphestTask.php
+++ b/src/applications/maniphest/storage/ManiphestTask.php
@@ -213,6 +213,10 @@
return ManiphestTaskStatus::isClosedStatus($this->getStatus());
}
+ public function isLocked() {
+ return ManiphestTaskStatus::isLockedStatus($this->getStatus());
+ }
+
public function setProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
@@ -343,6 +347,7 @@
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_INTERACT,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
@@ -351,6 +356,12 @@
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
+ case PhabricatorPolicyCapability::CAN_INTERACT:
+ if ($this->isLocked()) {
+ return PhabricatorPolicies::POLICY_NOONE;
+ } else {
+ return PhabricatorPolicies::POLICY_USER;
+ }
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
diff --git a/src/applications/policy/capability/PhabricatorPolicyCapability.php b/src/applications/policy/capability/PhabricatorPolicyCapability.php
--- a/src/applications/policy/capability/PhabricatorPolicyCapability.php
+++ b/src/applications/policy/capability/PhabricatorPolicyCapability.php
@@ -5,6 +5,7 @@
const CAN_VIEW = 'view';
const CAN_EDIT = 'edit';
const CAN_JOIN = 'join';
+ const CAN_INTERACT = 'interact';
/**
* Get the unique key identifying this capability. This key must be globally
diff --git a/src/applications/policy/filter/PhabricatorPolicyFilter.php b/src/applications/policy/filter/PhabricatorPolicyFilter.php
--- a/src/applications/policy/filter/PhabricatorPolicyFilter.php
+++ b/src/applications/policy/filter/PhabricatorPolicyFilter.php
@@ -86,6 +86,36 @@
return (count($result) == 1);
}
+ public static function canInteract(
+ PhabricatorUser $user,
+ PhabricatorPolicyInterface $object) {
+
+ $capabilities = $object->getCapabilities();
+ $capabilities = array_fuse($capabilities);
+
+ $can_interact = PhabricatorPolicyCapability::CAN_INTERACT;
+ $can_view = PhabricatorPolicyCapability::CAN_VIEW;
+
+ $require = array();
+
+ // If the object doesn't support a separate "Interact" capability, we
+ // only use the "View" capability: for most objects, you can interact
+ // with them if you can see them.
+ $require[] = $can_view;
+
+ if (isset($capabilities[$can_interact])) {
+ $require[] = $can_interact;
+ }
+
+ foreach ($require as $capability) {
+ if (!self::hasCapability($user, $object, $capability)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
public function setViewer(PhabricatorUser $user) {
$this->viewer = $user;
return $this;
diff --git a/src/applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php b/src/applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php
--- a/src/applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php
+++ b/src/applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php
@@ -47,6 +47,19 @@
$handle->getURI());
}
+ if (!PhabricatorPolicyFilter::canInteract($viewer, $object)) {
+ if ($is_add) {
+ $message = pht('You can not subscribe to a locked object.');
+ } else {
+ $message = pht('You can not unsubscribe from a locked object.');
+ }
+
+ return $this->buildErrorResponse(
+ pht('Object Locked'),
+ $message,
+ $handle->getURI());
+ }
+
if ($object instanceof PhabricatorApplicationTransactionInterface) {
if ($is_add) {
$xaction_value = array(
diff --git a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php
--- a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php
+++ b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php
@@ -56,20 +56,24 @@
$subscribed = isset($edges[$src_phid][$edge_type][$user_phid]);
}
+ $can_interact = PhabricatorPolicyFilter::canInteract($user, $object);
+
if ($subscribed) {
$sub_action = id(new PhabricatorActionView())
->setWorkflow(true)
->setRenderAsForm(true)
->setHref('/subscriptions/delete/'.$object->getPHID().'/')
->setName(pht('Unsubscribe'))
- ->setIcon('fa-minus-circle');
+ ->setIcon('fa-minus-circle')
+ ->setDisabled(!$can_interact);
} else {
$sub_action = id(new PhabricatorActionView())
->setWorkflow(true)
->setRenderAsForm(true)
->setHref('/subscriptions/add/'.$object->getPHID().'/')
->setName(pht('Subscribe'))
- ->setIcon('fa-plus-circle');
+ ->setIcon('fa-plus-circle')
+ ->setDisabled(!$can_interact);
}
if (!$user->isLoggedIn()) {
diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php
--- a/src/applications/transactions/editengine/PhabricatorEditEngine.php
+++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php
@@ -985,9 +985,31 @@
$fields = $this->buildEditFields($object);
$template = $object->getApplicationTransactionTemplate();
+ if ($this->getIsCreate()) {
+ $cancel_uri = $this->getObjectCreateCancelURI($object);
+ $submit_button = $this->getObjectCreateButtonText($object);
+ } else {
+ $cancel_uri = $this->getEffectiveObjectEditCancelURI($object);
+ $submit_button = $this->getObjectEditButtonText($object);
+ }
+
$config = $this->getEditEngineConfiguration()
->attachEngine($this);
+ $can_interact = PhabricatorPolicyFilter::canInteract($viewer, $object);
+ if (!$can_interact &&
+ !$request->getBool('editEngine') &&
+ !$request->getBool('overrideLock')) {
+ return $this->getController()
+ ->newDialog()
+ ->setTitle(pht('Edit Locked Object'))
+ ->appendParagraph(pht('This object is locked. Edit it anyway?'))
+ ->addHiddenInput('overrideLock', true)
+ ->setDisableWorkflowOnSubmit(true)
+ ->addCancelButton($cancel_uri)
+ ->addSubmitButton(pht('Override Lock'));
+ }
+
$validation_exception = null;
if ($request->isFormPost() && $request->getBool('editEngine')) {
$submit_fields = $fields;
@@ -1154,14 +1176,6 @@
$form = $this->buildEditForm($object, $fields);
if ($request->isAjax()) {
- if ($this->getIsCreate()) {
- $cancel_uri = $this->getObjectCreateCancelURI($object);
- $submit_button = $this->getObjectCreateButtonText($object);
- } else {
- $cancel_uri = $this->getEffectiveObjectEditCancelURI($object);
- $submit_button = $this->getObjectEditButtonText($object);
- }
-
return $this->getController()
->newDialog()
->setWidth(AphrontDialogView::WIDTH_FULL)
@@ -1554,8 +1568,14 @@
}
$viewer = $this->getViewer();
- $object_phid = $object->getPHID();
+ $can_interact = PhabricatorPolicyFilter::canInteract($viewer, $object);
+ if (!$can_interact) {
+ return id(new PhabricatorApplicationTransactionCommentView())
+ ->setIsLocked(true);
+ }
+
+ $object_phid = $object->getPHID();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
if ($is_serious) {
@@ -1761,6 +1781,14 @@
$config->getName()));
}
+ private function buildLockedObjectResponse($object) {
+ return $this->buildError(
+ $object,
+ pht('Object Locked'),
+ pht(
+ 'This object has been locked, so you can not comment on it.'));
+ }
+
private function buildCommentResponse($object) {
$viewer = $this->getViewer();
@@ -1775,6 +1803,11 @@
return new Aphront400Response();
}
+ $can_interact = PhabricatorPolicyFilter::canInteract($viewer, $object);
+ if (!$can_interact) {
+ return $this->buildLockedObjectResponse($object);
+ }
+
$config = $this->loadDefaultEditConfiguration($object);
if (!$config) {
return new Aphront404Response();
diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
--- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
+++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
@@ -22,6 +22,7 @@
private $noPermission;
private $fullWidth;
private $infoView;
+ private $isLocked;
private $currentVersion;
private $versionedDraft;
@@ -149,11 +150,20 @@
return $this->noPermission;
}
+ public function setIsLocked($is_locked) {
+ $this->isLocked = $is_locked;
+ return $this;
+ }
+
+ public function getIsLocked() {
+ return $this->isLocked;
+ }
+
public function setTransactionTimeline(
PhabricatorApplicationTransactionView $timeline) {
$timeline->setQuoteTargetID($this->getCommentID());
- if ($this->getNoPermission()) {
+ if ($this->getNoPermission() || $this->getIsLocked()) {
$timeline->setShouldTerminate(true);
}
@@ -166,6 +176,15 @@
return null;
}
+ if ($this->getIsLocked()) {
+ return id(new PHUIInfoView())
+ ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
+ ->setErrors(
+ array(
+ pht('This object has been locked.'),
+ ));
+ }
+
$user = $this->getUser();
if (!$user->isLoggedIn()) {
$uri = id(new PhutilURI('/login/'))
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Mar 17, 2:41 AM (6 d, 23 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7707946
Default Alt Text
D17453.id41964.diff (12 KB)
Attached To
Mode
D17453: Allow task statuses to "lock" them, preventing additional comments and interactions
Attached
Detach File
Event Timeline
Log In to Comment