Page MenuHomePhabricator

D10508.id25278.diff
No OneTemporary

D10508.id25278.diff

diff --git a/resources/sql/autopatches/20140917.project.canlock.sql b/resources/sql/autopatches/20140917.project.canlock.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20140917.project.canlock.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_project.project
+ ADD isMembershipLocked TINYINT(1) NOT NULL DEFAULT 0 AFTER joinPolicy;
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
@@ -2669,6 +2669,7 @@
'PonderVoteEditor' => 'applications/ponder/editor/PonderVoteEditor.php',
'PonderVoteSaveController' => 'applications/ponder/controller/PonderVoteSaveController.php',
'ProjectBoardTaskCard' => 'applications/project/view/ProjectBoardTaskCard.php',
+ 'ProjectCanLockProjectsCapability' => 'applications/project/capability/ProjectCanLockProjectsCapability.php',
'ProjectConduitAPIMethod' => 'applications/project/conduit/ProjectConduitAPIMethod.php',
'ProjectCreateConduitAPIMethod' => 'applications/project/conduit/ProjectCreateConduitAPIMethod.php',
'ProjectCreateProjectsCapability' => 'applications/project/capability/ProjectCreateProjectsCapability.php',
@@ -5696,6 +5697,7 @@
'PonderVote' => 'PonderConstants',
'PonderVoteEditor' => 'PhabricatorEditor',
'PonderVoteSaveController' => 'PonderController',
+ 'ProjectCanLockProjectsCapability' => 'PhabricatorPolicyCapability',
'ProjectConduitAPIMethod' => 'ConduitAPIMethod',
'ProjectCreateConduitAPIMethod' => 'ProjectConduitAPIMethod',
'ProjectCreateProjectsCapability' => 'PhabricatorPolicyCapability',
diff --git a/src/applications/project/application/PhabricatorProjectApplication.php b/src/applications/project/application/PhabricatorProjectApplication.php
--- a/src/applications/project/application/PhabricatorProjectApplication.php
+++ b/src/applications/project/application/PhabricatorProjectApplication.php
@@ -93,6 +93,9 @@
protected function getCustomCapabilities() {
return array(
ProjectCreateProjectsCapability::CAPABILITY => array(),
+ ProjectCanLockProjectsCapability::CAPABILITY => array(
+ 'default' => PhabricatorPolicies::POLICY_ADMIN,
+ ),
);
}
diff --git a/src/applications/project/capability/ProjectCanLockProjectsCapability.php b/src/applications/project/capability/ProjectCanLockProjectsCapability.php
new file mode 100644
--- /dev/null
+++ b/src/applications/project/capability/ProjectCanLockProjectsCapability.php
@@ -0,0 +1,16 @@
+<?php
+
+final class ProjectCanLockProjectsCapability
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'project.can.lock';
+
+ public function getCapabilityName() {
+ return pht('Can Lock Project Membership');
+ }
+
+ public function describeCapabilityRejection() {
+ return pht('You do not have permission to lock project membership.');
+ }
+
+}
diff --git a/src/applications/project/controller/PhabricatorProjectEditDetailsController.php b/src/applications/project/controller/PhabricatorProjectEditDetailsController.php
--- a/src/applications/project/controller/PhabricatorProjectEditDetailsController.php
+++ b/src/applications/project/controller/PhabricatorProjectEditDetailsController.php
@@ -49,6 +49,7 @@
$v_slugs = $project_slugs;
$v_color = $project->getColor();
$v_icon = $project->getIcon();
+ $v_locked = $project->getIsMembershipLocked();
$validation_exception = null;
@@ -63,6 +64,7 @@
$v_join = $request->getStr('can_join');
$v_color = $request->getStr('color');
$v_icon = $request->getStr('icon');
+ $v_locked = $request->getInt('is_membership_locked', 0);
$xactions = $field_list->buildFieldTransactionsFromRequest(
new PhabricatorProjectTransaction(),
@@ -73,6 +75,7 @@
$type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
$type_icon = PhabricatorProjectTransaction::TYPE_ICON;
$type_color = PhabricatorProjectTransaction::TYPE_COLOR;
+ $type_locked = PhabricatorProjectTransaction::TYPE_LOCKED;
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType($type_name)
@@ -102,6 +105,10 @@
->setTransactionType($type_color)
->setNewValue($v_color);
+ $xactions[] = id(new PhabricatorProjectTransaction())
+ ->setTransactionType($type_locked)
+ ->setNewValue($v_locked);
+
$editor = id(new PhabricatorProjectTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
@@ -148,6 +155,10 @@
$icon_uri = $this->getApplicationURI('icon/'.$project->getID().'/');
$icon_display = PhabricatorProjectIcon::renderIconForChooser($v_icon);
+ list($can_lock, $lock_message) = $this->explainApplicationCapability(
+ ProjectCanLockProjectsCapability::CAPABILITY,
+ pht('You can update the Lock Project setting.'),
+ pht('You can not update the Lock Project setting.'));
$form
->appendChild(
@@ -202,6 +213,16 @@
->setPolicies($policies)
->setCapability(PhabricatorPolicyCapability::CAN_JOIN))
->appendChild(
+ id(new AphrontFormCheckboxControl())
+ ->setLabel(pht('Lock Project'))
+ ->setDisabled(!$can_lock)
+ ->addCheckbox(
+ 'is_membership_locked',
+ 1,
+ pht('Prevent members from leaving this project.'),
+ $v_locked)
+ ->setCaption($lock_message))
+ ->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($edit_uri)
->setValue(pht('Save')));
diff --git a/src/applications/project/controller/PhabricatorProjectUpdateController.php b/src/applications/project/controller/PhabricatorProjectUpdateController.php
--- a/src/applications/project/controller/PhabricatorProjectUpdateController.php
+++ b/src/applications/project/controller/PhabricatorProjectUpdateController.php
@@ -82,12 +82,18 @@
case 'leave':
$dialog = new AphrontDialogView();
$dialog->setUser($user);
- $dialog->setTitle(pht('Really leave project?'));
- $dialog->appendChild(phutil_tag('p', array(), pht(
- 'Your tremendous contributions to this project will be sorely '.
- 'missed. Are you sure you want to leave?')));
+ if ($this->userCannotLeave($project)) {
+ $dialog->setTitle(pht('You can not leave this project.'));
+ $body = pht('The membership is locked for this project.');
+ } else {
+ $dialog->setTitle(pht('Really leave project?'));
+ $body = pht(
+ 'Your tremendous contributions to this project will be sorely '.
+ 'missed. Are you sure you want to leave?');
+ $dialog->addSubmitButton(pht('Leave Project'));
+ }
+ $dialog->appendParagraph($body);
$dialog->addCancelButton($project_uri);
- $dialog->addSubmitButton(pht('Leave Project'));
break;
default:
return new Aphront404Response();
@@ -96,4 +102,18 @@
return id(new AphrontDialogResponse())->setDialog($dialog);
}
+ /**
+ * This is enforced in @{class:PhabricatorProjectTransactionEditor}. We use
+ * this logic to render a better form for users hitting this case.
+ */
+ private function userCannotLeave(PhabricatorProject $project) {
+ $user = $this->getRequest()->getUser();
+
+ return
+ $project->getIsMembershipLocked() &&
+ !PhabricatorPolicyFilter::hasCapability(
+ $user,
+ $project,
+ PhabricatorPolicyCapability::CAN_EDIT);
+ }
}
diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php
--- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php
+++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php
@@ -25,6 +25,7 @@
$types[] = PhabricatorProjectTransaction::TYPE_IMAGE;
$types[] = PhabricatorProjectTransaction::TYPE_ICON;
$types[] = PhabricatorProjectTransaction::TYPE_COLOR;
+ $types[] = PhabricatorProjectTransaction::TYPE_LOCKED;
return $types;
}
@@ -49,6 +50,8 @@
return $object->getIcon();
case PhabricatorProjectTransaction::TYPE_COLOR:
return $object->getColor();
+ case PhabricatorProjectTransaction::TYPE_LOCKED:
+ return (int) $object->getIsMembershipLocked();
}
return parent::getCustomTransactionOldValue($object, $xaction);
@@ -65,6 +68,7 @@
case PhabricatorProjectTransaction::TYPE_IMAGE:
case PhabricatorProjectTransaction::TYPE_ICON:
case PhabricatorProjectTransaction::TYPE_COLOR:
+ case PhabricatorProjectTransaction::TYPE_LOCKED:
return $xaction->getNewValue();
}
@@ -94,6 +98,9 @@
case PhabricatorProjectTransaction::TYPE_COLOR:
$object->setColor($xaction->getNewValue());
return;
+ case PhabricatorProjectTransaction::TYPE_LOCKED:
+ $object->setIsMembershipLocked($xaction->getNewValue());
+ return;
case PhabricatorTransactions::TYPE_EDGE:
return;
case PhabricatorTransactions::TYPE_VIEW_POLICY:
@@ -199,6 +206,7 @@
case PhabricatorProjectTransaction::TYPE_IMAGE:
case PhabricatorProjectTransaction::TYPE_ICON:
case PhabricatorProjectTransaction::TYPE_COLOR:
+ case PhabricatorProjectTransaction::TYPE_LOCKED:
return;
case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $xaction->getMetadataValue('edge:type');
@@ -360,6 +368,7 @@
}
break;
+
}
return $errors;
@@ -381,6 +390,12 @@
$object,
PhabricatorPolicyCapability::CAN_EDIT);
return;
+ case PhabricatorProjectTransaction::TYPE_LOCKED:
+ PhabricatorPolicyFilter::requireCapability(
+ $this->requireActor(),
+ newv($this->getEditorApplicationClass(), array()),
+ ProjectCanLockProjectsCapability::CAPABILITY);
+ return;
case PhabricatorTransactions::TYPE_EDGE:
switch ($xaction->getMetadataValue('edge:type')) {
case PhabricatorEdgeConfig::TYPE_PROJ_MEMBER:
@@ -401,9 +416,20 @@
$this->requireActor(),
$object,
PhabricatorPolicyCapability::CAN_JOIN);
- } else if ($is_leave) {
- // You don't need any capabilities to leave a project.
- } else {
+ }
+
+ if ($is_leave) {
+ // You usually don't need any capabilities to leave a project.
+ if ($object->getIsMembershipLocked()) {
+ // you must be able to edit though to leave locked projects
+ PhabricatorPolicyFilter::requireCapability(
+ $this->requireActor(),
+ $object,
+ PhabricatorPolicyCapability::CAN_EDIT);
+ }
+ }
+
+ if (count($add) > 1 || count($rem) > 1) {
// You need CAN_EDIT to change members other than yourself.
PhabricatorPolicyFilter::requireCapability(
$this->requireActor(),
diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php
--- a/src/applications/project/storage/PhabricatorProject.php
+++ b/src/applications/project/storage/PhabricatorProject.php
@@ -20,6 +20,7 @@
protected $viewPolicy;
protected $editPolicy;
protected $joinPolicy;
+ protected $isMembershipLocked;
private $memberPHIDs = self::ATTACHABLE;
private $watcherPHIDs = self::ATTACHABLE;
@@ -43,6 +44,7 @@
->setViewPolicy(PhabricatorPolicies::POLICY_USER)
->setEditPolicy(PhabricatorPolicies::POLICY_USER)
->setJoinPolicy(PhabricatorPolicies::POLICY_USER)
+ ->setIsMembershipLocked(0)
->attachMemberPHIDs(array());
}
diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php
--- a/src/applications/project/storage/PhabricatorProjectTransaction.php
+++ b/src/applications/project/storage/PhabricatorProjectTransaction.php
@@ -9,6 +9,7 @@
const TYPE_IMAGE = 'project:image';
const TYPE_ICON = 'project:icon';
const TYPE_COLOR = 'project:color';
+ const TYPE_LOCKED = 'project:locked';
// NOTE: This is deprecated, members are just a normal edge now.
const TYPE_MEMBERS = 'project:members';
@@ -100,6 +101,17 @@
$author_handle,
PHUITagView::getShadeName($new));
+ case PhabricatorProjectTransaction::TYPE_LOCKED:
+ if ($new) {
+ return pht(
+ '%s locked this project\'s membership.',
+ $author_handle);
+ } else {
+ return pht(
+ '%s unlocked this project\'s membership.',
+ $author_handle);
+ }
+
case PhabricatorProjectTransaction::TYPE_SLUGS:
$add = array_diff($new, $old);
$rem = array_diff($old, $new);

File Metadata

Mime Type
text/plain
Expires
Wed, Feb 12, 1:37 AM (17 h, 6 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7123501
Default Alt Text
D10508.id25278.diff (12 KB)

Event Timeline