Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15428508
D17453.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
21 KB
Referenced Files
None
Subscribers
None
D17453.diff
View Options
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
@@ -1487,6 +1487,7 @@
'ManiphestTaskDetailController' => 'applications/maniphest/controller/ManiphestTaskDetailController.php',
'ManiphestTaskEditBulkJobType' => 'applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php',
'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php',
+ 'ManiphestTaskEditEngineLock' => 'applications/maniphest/editor/ManiphestTaskEditEngineLock.php',
'ManiphestTaskFulltextEngine' => 'applications/maniphest/search/ManiphestTaskFulltextEngine.php',
'ManiphestTaskGraph' => 'infrastructure/graph/ManiphestTaskGraph.php',
'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php',
@@ -2617,9 +2618,12 @@
'PhabricatorEditEngineConfigurationViewController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php',
'PhabricatorEditEngineController' => 'applications/transactions/controller/PhabricatorEditEngineController.php',
'PhabricatorEditEngineDatasource' => 'applications/transactions/typeahead/PhabricatorEditEngineDatasource.php',
+ 'PhabricatorEditEngineDefaultLock' => 'applications/transactions/editengine/PhabricatorEditEngineDefaultLock.php',
'PhabricatorEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorEditEngineExtension.php',
'PhabricatorEditEngineExtensionModule' => 'applications/transactions/engineextension/PhabricatorEditEngineExtensionModule.php',
'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php',
+ 'PhabricatorEditEngineLock' => 'applications/transactions/editengine/PhabricatorEditEngineLock.php',
+ 'PhabricatorEditEngineLockableInterface' => 'applications/transactions/editengine/PhabricatorEditEngineLockableInterface.php',
'PhabricatorEditEnginePointsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEnginePointsCommentAction.php',
'PhabricatorEditEngineProfileMenuItem' => 'applications/search/menuitem/PhabricatorEditEngineProfileMenuItem.php',
'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php',
@@ -6372,6 +6376,7 @@
'PhabricatorFulltextInterface',
'DoorkeeperBridgedObjectInterface',
'PhabricatorEditEngineSubtypeInterface',
+ 'PhabricatorEditEngineLockableInterface',
),
'ManiphestTaskAssignHeraldAction' => 'HeraldAction',
'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction',
@@ -6387,6 +6392,7 @@
'ManiphestTaskDetailController' => 'ManiphestController',
'ManiphestTaskEditBulkJobType' => 'PhabricatorWorkerBulkJobType',
'ManiphestTaskEditController' => 'ManiphestController',
+ 'ManiphestTaskEditEngineLock' => 'PhabricatorEditEngineLock',
'ManiphestTaskFulltextEngine' => 'PhabricatorFulltextEngine',
'ManiphestTaskGraph' => 'PhabricatorObjectGraph',
'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType',
@@ -7679,9 +7685,11 @@
'PhabricatorEditEngineConfigurationViewController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineController' => 'PhabricatorApplicationTransactionController',
'PhabricatorEditEngineDatasource' => 'PhabricatorTypeaheadDatasource',
+ 'PhabricatorEditEngineDefaultLock' => 'PhabricatorEditEngineLock',
'PhabricatorEditEngineExtension' => 'Phobject',
'PhabricatorEditEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorEditEngineListController' => 'PhabricatorEditEngineController',
+ 'PhabricatorEditEngineLock' => 'Phobject',
'PhabricatorEditEnginePointsCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEngineProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorEditEngineQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
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/editor/ManiphestTaskEditEngineLock.php b/src/applications/maniphest/editor/ManiphestTaskEditEngineLock.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/editor/ManiphestTaskEditEngineLock.php
@@ -0,0 +1,28 @@
+<?php
+
+final class ManiphestTaskEditEngineLock
+ extends PhabricatorEditEngineLock {
+
+ public function willPromptUserForLockOverrideWithDialog(
+ AphrontDialogView $dialog) {
+
+ return $dialog
+ ->setTitle(pht('Edit Locked Task'))
+ ->appendParagraph(pht('This task is locked. Edit it anyway?'))
+ ->addSubmitButton(pht('Override Task Lock'));
+ }
+
+ public function willBlockUserInteractionWithDialog(
+ AphrontDialogView $dialog) {
+
+ return $dialog
+ ->setTitle(pht('Task Locked'))
+ ->appendParagraph(
+ pht('You can not interact with this task because it is locked.'));
+ }
+
+ public function getLockedObjectDisplayText() {
+ return pht('This task has been locked.');
+ }
+
+}
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
@@ -17,7 +17,8 @@
PhabricatorConduitResultInterface,
PhabricatorFulltextInterface,
DoorkeeperBridgedObjectInterface,
- PhabricatorEditEngineSubtypeInterface {
+ PhabricatorEditEngineSubtypeInterface,
+ PhabricatorEditEngineLockableInterface {
const MARKUP_FIELD_DESCRIPTION = 'markup:desc';
@@ -213,6 +214,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 +348,7 @@
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_INTERACT,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
@@ -351,6 +357,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();
}
@@ -562,4 +574,12 @@
return PhabricatorEditEngineSubtype::newSubtypeMap($config);
}
+
+/* -( PhabricatorEditEngineLockableInterface )----------------------------- */
+
+
+ public function newEditEngineLock() {
+ return new ManiphestTaskEditEngineLock();
+ }
+
}
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,15 @@
$handle->getURI());
}
+ if (!PhabricatorPolicyFilter::canInteract($viewer, $object)) {
+ $lock = PhabricatorEditEngineLock::newForObject($viewer, $object);
+
+ $dialog = $this->newDialog()
+ ->addCancelButton($handle->getURI());
+
+ return $lock->willBlockUserInteractionWithDialog($dialog);
+ }
+
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,33 @@
$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')) {
+
+ $lock = PhabricatorEditEngineLock::newForObject($viewer, $object);
+
+ $dialog = $this->getController()
+ ->newDialog()
+ ->addHiddenInput('overrideLock', true)
+ ->setDisableWorkflowOnSubmit(true)
+ ->addCancelButton($cancel_uri);
+
+ return $lock->willPromptUserForLockOverrideWithDialog($dialog);
+ }
+
$validation_exception = null;
if ($request->isFormPost() && $request->getBool('editEngine')) {
$submit_fields = $fields;
@@ -1154,14 +1178,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 +1570,16 @@
}
$viewer = $this->getViewer();
- $object_phid = $object->getPHID();
+ $can_interact = PhabricatorPolicyFilter::canInteract($viewer, $object);
+ if (!$can_interact) {
+ $lock = PhabricatorEditEngineLock::newForObject($viewer, $object);
+
+ return id(new PhabricatorApplicationTransactionCommentView())
+ ->setEditEngineLock($lock);
+ }
+
+ $object_phid = $object->getPHID();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
if ($is_serious) {
@@ -1700,11 +1724,19 @@
private function buildError($object, $title, $body) {
$cancel_uri = $this->getObjectCreateCancelURI($object);
- return $this->getController()
+ $dialog = $this->getController()
->newDialog()
- ->setTitle($title)
- ->appendParagraph($body)
->addCancelButton($cancel_uri);
+
+ if ($title !== null) {
+ $dialog->setTitle($title);
+ }
+
+ if ($body !== null) {
+ $dialog->appendParagraph($body);
+ }
+
+ return $dialog;
}
@@ -1761,6 +1793,14 @@
$config->getName()));
}
+ private function buildLockedObjectResponse($object) {
+ $dialog = $this->buildError($object, null, null);
+ $viewer = $this->getViewer();
+
+ $lock = PhabricatorEditEngineLock::newForObject($viewer, $object);
+ return $lock->willBlockUserInteractionWithDialog($dialog);
+ }
+
private function buildCommentResponse($object) {
$viewer = $this->getViewer();
@@ -1775,6 +1815,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/editengine/PhabricatorEditEngineDefaultLock.php b/src/applications/transactions/editengine/PhabricatorEditEngineDefaultLock.php
new file mode 100644
--- /dev/null
+++ b/src/applications/transactions/editengine/PhabricatorEditEngineDefaultLock.php
@@ -0,0 +1,4 @@
+<?php
+
+final class PhabricatorEditEngineDefaultLock
+ extends PhabricatorEditEngineLock {}
diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineLock.php b/src/applications/transactions/editengine/PhabricatorEditEngineLock.php
new file mode 100644
--- /dev/null
+++ b/src/applications/transactions/editengine/PhabricatorEditEngineLock.php
@@ -0,0 +1,66 @@
+<?php
+
+abstract class PhabricatorEditEngineLock
+ extends Phobject {
+
+ private $viewer;
+ private $object;
+
+ final public function setViewer(PhabricatorUser $viewer) {
+ $this->viewer = $viewer;
+ return $this;
+ }
+
+ final public function getViewer() {
+ return $this->viewer;
+ }
+
+ final public function setObject($object) {
+ $this->object = $object;
+ return $this;
+ }
+
+ final public function getObject() {
+ return $this->object;
+ }
+
+ public function willPromptUserForLockOverrideWithDialog(
+ AphrontDialogView $dialog) {
+
+ return $dialog
+ ->setTitle(pht('Edit Locked Object'))
+ ->appendParagraph(pht('This object is locked. Edit it anyway?'))
+ ->addSubmitButton(pht('Override Lock'));
+ }
+
+ public function willBlockUserInteractionWithDialog(
+ AphrontDialogView $dialog) {
+
+ return $dialog
+ ->setTitle(pht('Object Locked'))
+ ->appendParagraph(
+ pht('You can not interact with this object because it is locked.'));
+ }
+
+ public function getLockedObjectDisplayText() {
+ return pht('This object has been locked.');
+ }
+
+ public static function newForObject(
+ PhabricatorUser $viewer,
+ $object) {
+
+ if ($object instanceof PhabricatorEditEngineLockableInterface) {
+ $lock = $object->newEditEngineLock();
+ } else {
+ $lock = new PhabricatorEditEngineDefaultLock();
+ }
+
+ return $lock
+ ->setViewer($viewer)
+ ->setObject($object);
+ }
+
+
+
+}
diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineLockableInterface.php b/src/applications/transactions/editengine/PhabricatorEditEngineLockableInterface.php
new file mode 100644
--- /dev/null
+++ b/src/applications/transactions/editengine/PhabricatorEditEngineLockableInterface.php
@@ -0,0 +1,7 @@
+<?php
+
+interface PhabricatorEditEngineLockableInterface {
+
+ public function newEditEngineLock();
+
+}
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 $editEngineLock;
private $currentVersion;
private $versionedDraft;
@@ -149,11 +150,20 @@
return $this->noPermission;
}
+ public function setEditEngineLock(PhabricatorEditEngineLock $lock) {
+ $this->editEngineLock = $lock;
+ return $this;
+ }
+
+ public function getEditEngineLock() {
+ return $this->editEngineLock;
+ }
+
public function setTransactionTimeline(
PhabricatorApplicationTransactionView $timeline) {
$timeline->setQuoteTargetID($this->getCommentID());
- if ($this->getNoPermission()) {
+ if ($this->getNoPermission() || $this->getEditEngineLock()) {
$timeline->setShouldTerminate(true);
}
@@ -166,6 +176,16 @@
return null;
}
+ $lock = $this->getEditEngineLock();
+ if ($lock) {
+ return id(new PHUIInfoView())
+ ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
+ ->setErrors(
+ array(
+ $lock->getLockedObjectDisplayText(),
+ ));
+ }
+
$user = $this->getUser();
if (!$user->isLoggedIn()) {
$uri = id(new PhutilURI('/login/'))
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Mar 24, 8:52 PM (2 d, 20 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7691333
Default Alt Text
D17453.diff (21 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