@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Projects - add "lock membership", which prevents people from leaving

Summary:
Fixes T5603. Puts the toggling of locking membership into the editor so we get exceptions and all that.

I think the dialogue when you try to leave a project that is locked could be a little better maybe? Right now it just says "You can't leave" and "The membership is locked" more or less; should I surface a link to the policy stuff there too?

Test Plan:
- made a project, toggled the "lock" setting, observed stickiness and good transactions being made
- locked a project and tried to leave as a non-editor - got a dialogue letting me know i couldn't
- locked a project and tried to leave as an editor - left successfully

Reviewers: epriestley

Reviewed By: epriestley

Subscribers: epriestley, Korvin

Maniphest Tasks: T5603

Differential Revision: https://secure.phabricator.com/D10508

+106 -6
+2
resources/sql/autopatches/20140917.project.canlock.sql
··· 1 + ALTER TABLE {$NAMESPACE}_project.project 2 + ADD isMembershipLocked TINYINT(1) NOT NULL DEFAULT 0 AFTER joinPolicy;
+2
src/__phutil_library_map__.php
··· 2682 2682 'PonderVoteEditor' => 'applications/ponder/editor/PonderVoteEditor.php', 2683 2683 'PonderVoteSaveController' => 'applications/ponder/controller/PonderVoteSaveController.php', 2684 2684 'ProjectBoardTaskCard' => 'applications/project/view/ProjectBoardTaskCard.php', 2685 + 'ProjectCanLockProjectsCapability' => 'applications/project/capability/ProjectCanLockProjectsCapability.php', 2685 2686 'ProjectConduitAPIMethod' => 'applications/project/conduit/ProjectConduitAPIMethod.php', 2686 2687 'ProjectCreateConduitAPIMethod' => 'applications/project/conduit/ProjectCreateConduitAPIMethod.php', 2687 2688 'ProjectCreateProjectsCapability' => 'applications/project/capability/ProjectCreateProjectsCapability.php', ··· 5722 5723 'PonderVote' => 'PonderConstants', 5723 5724 'PonderVoteEditor' => 'PhabricatorEditor', 5724 5725 'PonderVoteSaveController' => 'PonderController', 5726 + 'ProjectCanLockProjectsCapability' => 'PhabricatorPolicyCapability', 5725 5727 'ProjectConduitAPIMethod' => 'ConduitAPIMethod', 5726 5728 'ProjectCreateConduitAPIMethod' => 'ProjectConduitAPIMethod', 5727 5729 'ProjectCreateProjectsCapability' => 'PhabricatorPolicyCapability',
+3
src/applications/project/application/PhabricatorProjectApplication.php
··· 93 93 protected function getCustomCapabilities() { 94 94 return array( 95 95 ProjectCreateProjectsCapability::CAPABILITY => array(), 96 + ProjectCanLockProjectsCapability::CAPABILITY => array( 97 + 'default' => PhabricatorPolicies::POLICY_ADMIN, 98 + ), 96 99 ); 97 100 } 98 101
+16
src/applications/project/capability/ProjectCanLockProjectsCapability.php
··· 1 + <?php 2 + 3 + final class ProjectCanLockProjectsCapability 4 + extends PhabricatorPolicyCapability { 5 + 6 + const CAPABILITY = 'project.can.lock'; 7 + 8 + public function getCapabilityName() { 9 + return pht('Can Lock Project Membership'); 10 + } 11 + 12 + public function describeCapabilityRejection() { 13 + return pht('You do not have permission to lock project membership.'); 14 + } 15 + 16 + }
+21
src/applications/project/controller/PhabricatorProjectEditDetailsController.php
··· 49 49 $v_slugs = $project_slugs; 50 50 $v_color = $project->getColor(); 51 51 $v_icon = $project->getIcon(); 52 + $v_locked = $project->getIsMembershipLocked(); 52 53 53 54 $validation_exception = null; 54 55 ··· 63 64 $v_join = $request->getStr('can_join'); 64 65 $v_color = $request->getStr('color'); 65 66 $v_icon = $request->getStr('icon'); 67 + $v_locked = $request->getInt('is_membership_locked', 0); 66 68 67 69 $xactions = $field_list->buildFieldTransactionsFromRequest( 68 70 new PhabricatorProjectTransaction(), ··· 73 75 $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; 74 76 $type_icon = PhabricatorProjectTransaction::TYPE_ICON; 75 77 $type_color = PhabricatorProjectTransaction::TYPE_COLOR; 78 + $type_locked = PhabricatorProjectTransaction::TYPE_LOCKED; 76 79 77 80 $xactions[] = id(new PhabricatorProjectTransaction()) 78 81 ->setTransactionType($type_name) ··· 102 105 ->setTransactionType($type_color) 103 106 ->setNewValue($v_color); 104 107 108 + $xactions[] = id(new PhabricatorProjectTransaction()) 109 + ->setTransactionType($type_locked) 110 + ->setNewValue($v_locked); 111 + 105 112 $editor = id(new PhabricatorProjectTransactionEditor()) 106 113 ->setActor($viewer) 107 114 ->setContentSourceFromRequest($request) ··· 148 155 149 156 $icon_uri = $this->getApplicationURI('icon/'.$project->getID().'/'); 150 157 $icon_display = PhabricatorProjectIcon::renderIconForChooser($v_icon); 158 + list($can_lock, $lock_message) = $this->explainApplicationCapability( 159 + ProjectCanLockProjectsCapability::CAPABILITY, 160 + pht('You can update the Lock Project setting.'), 161 + pht('You can not update the Lock Project setting.')); 151 162 152 163 $form 153 164 ->appendChild( ··· 201 212 ->setPolicyObject($project) 202 213 ->setPolicies($policies) 203 214 ->setCapability(PhabricatorPolicyCapability::CAN_JOIN)) 215 + ->appendChild( 216 + id(new AphrontFormCheckboxControl()) 217 + ->setLabel(pht('Lock Project')) 218 + ->setDisabled(!$can_lock) 219 + ->addCheckbox( 220 + 'is_membership_locked', 221 + 1, 222 + pht('Prevent members from leaving this project.'), 223 + $v_locked) 224 + ->setCaption($lock_message)) 204 225 ->appendChild( 205 226 id(new AphrontFormSubmitControl()) 206 227 ->addCancelButton($edit_uri)
+25 -5
src/applications/project/controller/PhabricatorProjectUpdateController.php
··· 82 82 case 'leave': 83 83 $dialog = new AphrontDialogView(); 84 84 $dialog->setUser($user); 85 - $dialog->setTitle(pht('Really leave project?')); 86 - $dialog->appendChild(phutil_tag('p', array(), pht( 87 - 'Your tremendous contributions to this project will be sorely '. 88 - 'missed. Are you sure you want to leave?'))); 85 + if ($this->userCannotLeave($project)) { 86 + $dialog->setTitle(pht('You can not leave this project.')); 87 + $body = pht('The membership is locked for this project.'); 88 + } else { 89 + $dialog->setTitle(pht('Really leave project?')); 90 + $body = pht( 91 + 'Your tremendous contributions to this project will be sorely '. 92 + 'missed. Are you sure you want to leave?'); 93 + $dialog->addSubmitButton(pht('Leave Project')); 94 + } 95 + $dialog->appendParagraph($body); 89 96 $dialog->addCancelButton($project_uri); 90 - $dialog->addSubmitButton(pht('Leave Project')); 91 97 break; 92 98 default: 93 99 return new Aphront404Response(); ··· 96 102 return id(new AphrontDialogResponse())->setDialog($dialog); 97 103 } 98 104 105 + /** 106 + * This is enforced in @{class:PhabricatorProjectTransactionEditor}. We use 107 + * this logic to render a better form for users hitting this case. 108 + */ 109 + private function userCannotLeave(PhabricatorProject $project) { 110 + $user = $this->getRequest()->getUser(); 111 + 112 + return 113 + $project->getIsMembershipLocked() && 114 + !PhabricatorPolicyFilter::hasCapability( 115 + $user, 116 + $project, 117 + PhabricatorPolicyCapability::CAN_EDIT); 118 + } 99 119 }
+23 -1
src/applications/project/editor/PhabricatorProjectTransactionEditor.php
··· 25 25 $types[] = PhabricatorProjectTransaction::TYPE_IMAGE; 26 26 $types[] = PhabricatorProjectTransaction::TYPE_ICON; 27 27 $types[] = PhabricatorProjectTransaction::TYPE_COLOR; 28 + $types[] = PhabricatorProjectTransaction::TYPE_LOCKED; 28 29 29 30 return $types; 30 31 } ··· 49 50 return $object->getIcon(); 50 51 case PhabricatorProjectTransaction::TYPE_COLOR: 51 52 return $object->getColor(); 53 + case PhabricatorProjectTransaction::TYPE_LOCKED: 54 + return (int) $object->getIsMembershipLocked(); 52 55 } 53 56 54 57 return parent::getCustomTransactionOldValue($object, $xaction); ··· 65 68 case PhabricatorProjectTransaction::TYPE_IMAGE: 66 69 case PhabricatorProjectTransaction::TYPE_ICON: 67 70 case PhabricatorProjectTransaction::TYPE_COLOR: 71 + case PhabricatorProjectTransaction::TYPE_LOCKED: 68 72 return $xaction->getNewValue(); 69 73 } 70 74 ··· 94 98 case PhabricatorProjectTransaction::TYPE_COLOR: 95 99 $object->setColor($xaction->getNewValue()); 96 100 return; 101 + case PhabricatorProjectTransaction::TYPE_LOCKED: 102 + $object->setIsMembershipLocked($xaction->getNewValue()); 103 + return; 97 104 case PhabricatorTransactions::TYPE_EDGE: 98 105 return; 99 106 case PhabricatorTransactions::TYPE_VIEW_POLICY: ··· 199 206 case PhabricatorProjectTransaction::TYPE_IMAGE: 200 207 case PhabricatorProjectTransaction::TYPE_ICON: 201 208 case PhabricatorProjectTransaction::TYPE_COLOR: 209 + case PhabricatorProjectTransaction::TYPE_LOCKED: 202 210 return; 203 211 case PhabricatorTransactions::TYPE_EDGE: 204 212 $edge_type = $xaction->getMetadataValue('edge:type'); ··· 360 368 } 361 369 362 370 break; 371 + 363 372 } 364 373 365 374 return $errors; ··· 381 390 $object, 382 391 PhabricatorPolicyCapability::CAN_EDIT); 383 392 return; 393 + case PhabricatorProjectTransaction::TYPE_LOCKED: 394 + PhabricatorPolicyFilter::requireCapability( 395 + $this->requireActor(), 396 + newv($this->getEditorApplicationClass(), array()), 397 + ProjectCanLockProjectsCapability::CAPABILITY); 398 + return; 384 399 case PhabricatorTransactions::TYPE_EDGE: 385 400 switch ($xaction->getMetadataValue('edge:type')) { 386 401 case PhabricatorEdgeConfig::TYPE_PROJ_MEMBER: ··· 402 417 $object, 403 418 PhabricatorPolicyCapability::CAN_JOIN); 404 419 } else if ($is_leave) { 405 - // You don't need any capabilities to leave a project. 420 + // You usually don't need any capabilities to leave a project. 421 + if ($object->getIsMembershipLocked()) { 422 + // you must be able to edit though to leave locked projects 423 + PhabricatorPolicyFilter::requireCapability( 424 + $this->requireActor(), 425 + $object, 426 + PhabricatorPolicyCapability::CAN_EDIT); 427 + } 406 428 } else { 407 429 // You need CAN_EDIT to change members other than yourself. 408 430 PhabricatorPolicyFilter::requireCapability(
+2
src/applications/project/storage/PhabricatorProject.php
··· 20 20 protected $viewPolicy; 21 21 protected $editPolicy; 22 22 protected $joinPolicy; 23 + protected $isMembershipLocked; 23 24 24 25 private $memberPHIDs = self::ATTACHABLE; 25 26 private $watcherPHIDs = self::ATTACHABLE; ··· 43 44 ->setViewPolicy(PhabricatorPolicies::POLICY_USER) 44 45 ->setEditPolicy(PhabricatorPolicies::POLICY_USER) 45 46 ->setJoinPolicy(PhabricatorPolicies::POLICY_USER) 47 + ->setIsMembershipLocked(0) 46 48 ->attachMemberPHIDs(array()); 47 49 } 48 50
+12
src/applications/project/storage/PhabricatorProjectTransaction.php
··· 9 9 const TYPE_IMAGE = 'project:image'; 10 10 const TYPE_ICON = 'project:icon'; 11 11 const TYPE_COLOR = 'project:color'; 12 + const TYPE_LOCKED = 'project:locked'; 12 13 13 14 // NOTE: This is deprecated, members are just a normal edge now. 14 15 const TYPE_MEMBERS = 'project:members'; ··· 99 100 '%s set this project\'s color to %s.', 100 101 $author_handle, 101 102 PHUITagView::getShadeName($new)); 103 + 104 + case PhabricatorProjectTransaction::TYPE_LOCKED: 105 + if ($new) { 106 + return pht( 107 + '%s locked this project\'s membership.', 108 + $author_handle); 109 + } else { 110 + return pht( 111 + '%s unlocked this project\'s membership.', 112 + $author_handle); 113 + } 102 114 103 115 case PhabricatorProjectTransaction::TYPE_SLUGS: 104 116 $add = array_diff($new, $old);