Page MenuHomePhabricator

D17453.diff
No OneTemporary

D17453.diff

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

Mime Type
text/plain
Expires
Thu, May 9, 1:23 AM (2 w, 5 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6265523
Default Alt Text
D17453.diff (21 KB)

Event Timeline