mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 00:42:41 +01:00
Move member/watch actions to "Members/Watchers" page
Summary: Ref T10054. This tries to make the members page a bit more consistent and provide hints to users about subproject/milestone membership rules. In particular: - You now join, leave, watch, unwatch, add and remove members, and lock and unlock membership from the members screen. - We now explain the membership rule for the project on this screen. There are currently four rules: - Normal Project: Join/leave normally. - Parent Project: Uses subprojects to determine members. - Milestone: Uses parent project to determine members. - Locked: Membership is locked. - (Future) Imported from LDAP/other external sources: Membership is determined by something else. Test Plan: {F1064878} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10054 Differential Revision: https://secure.phabricator.com/D15059
This commit is contained in:
parent
26500f0545
commit
6b1b21c999
15 changed files with 501 additions and 286 deletions
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
return array(
|
||||
'names' => array(
|
||||
'core.pkg.css' => '7fce81fc',
|
||||
'core.pkg.css' => 'bd4f3259',
|
||||
'core.pkg.js' => '573e6664',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => '2de124c9',
|
||||
|
@ -143,7 +143,7 @@ return array(
|
|||
'rsrc/css/phui/phui-object-item-list-view.css' => '26c30d3f',
|
||||
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
|
||||
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
|
||||
'rsrc/css/phui/phui-profile-menu.css' => '72d69773',
|
||||
'rsrc/css/phui/phui-profile-menu.css' => '84966ae9',
|
||||
'rsrc/css/phui/phui-property-list-view.css' => '27b2849e',
|
||||
'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591',
|
||||
'rsrc/css/phui/phui-spacing.css' => '042804d6',
|
||||
|
@ -819,7 +819,7 @@ return array(
|
|||
'phui-object-item-list-view-css' => '26c30d3f',
|
||||
'phui-pager-css' => 'bea33d23',
|
||||
'phui-pinboard-view-css' => '2495140e',
|
||||
'phui-profile-menu-css' => '72d69773',
|
||||
'phui-profile-menu-css' => '84966ae9',
|
||||
'phui-property-list-view-css' => '27b2849e',
|
||||
'phui-remarkup-preview-css' => '1a8f2591',
|
||||
'phui-spacing-css' => '042804d6',
|
||||
|
|
|
@ -2895,12 +2895,14 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectLogicalUserDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalUserDatasource.php',
|
||||
'PhabricatorProjectLogicalViewerDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php',
|
||||
'PhabricatorProjectMaterializedMemberEdgeType' => 'applications/project/edge/PhabricatorProjectMaterializedMemberEdgeType.php',
|
||||
'PhabricatorProjectMemberListView' => 'applications/project/view/PhabricatorProjectMemberListView.php',
|
||||
'PhabricatorProjectMemberOfProjectEdgeType' => 'applications/project/edge/PhabricatorProjectMemberOfProjectEdgeType.php',
|
||||
'PhabricatorProjectMembersAddController' => 'applications/project/controller/PhabricatorProjectMembersAddController.php',
|
||||
'PhabricatorProjectMembersDatasource' => 'applications/project/typeahead/PhabricatorProjectMembersDatasource.php',
|
||||
'PhabricatorProjectMembersEditController' => 'applications/project/controller/PhabricatorProjectMembersEditController.php',
|
||||
'PhabricatorProjectMembersPolicyRule' => 'applications/project/policyrule/PhabricatorProjectMembersPolicyRule.php',
|
||||
'PhabricatorProjectMembersProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectMembersProfilePanel.php',
|
||||
'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php',
|
||||
'PhabricatorProjectMembersViewController' => 'applications/project/controller/PhabricatorProjectMembersViewController.php',
|
||||
'PhabricatorProjectMilestonesController' => 'applications/project/controller/PhabricatorProjectMilestonesController.php',
|
||||
'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php',
|
||||
'PhabricatorProjectNameContextFreeGrammar' => 'applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php',
|
||||
|
@ -2932,8 +2934,10 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php',
|
||||
'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php',
|
||||
'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php',
|
||||
'PhabricatorProjectUserListView' => 'applications/project/view/PhabricatorProjectUserListView.php',
|
||||
'PhabricatorProjectViewController' => 'applications/project/controller/PhabricatorProjectViewController.php',
|
||||
'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php',
|
||||
'PhabricatorProjectWatcherListView' => 'applications/project/view/PhabricatorProjectWatcherListView.php',
|
||||
'PhabricatorProjectWorkboardProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php',
|
||||
'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php',
|
||||
'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php',
|
||||
|
@ -7292,12 +7296,14 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectLogicalUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorProjectLogicalViewerDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'PhabricatorProjectMaterializedMemberEdgeType' => 'PhabricatorEdgeType',
|
||||
'PhabricatorProjectMemberListView' => 'PhabricatorProjectUserListView',
|
||||
'PhabricatorProjectMemberOfProjectEdgeType' => 'PhabricatorEdgeType',
|
||||
'PhabricatorProjectMembersAddController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectMembersDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectMembersPolicyRule' => 'PhabricatorPolicyRule',
|
||||
'PhabricatorProjectMembersProfilePanel' => 'PhabricatorProfilePanel',
|
||||
'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectMembersViewController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectMilestonesController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectMoveController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectNameContextFreeGrammar' => 'PhutilContextFreeGrammar',
|
||||
|
@ -7332,8 +7338,10 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener',
|
||||
'PhabricatorProjectUpdateController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorProjectUserListView' => 'AphrontView',
|
||||
'PhabricatorProjectViewController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectWatchController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectWatcherListView' => 'PhabricatorProjectUserListView',
|
||||
'PhabricatorProjectWorkboardProfilePanel' => 'PhabricatorProfilePanel',
|
||||
'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension',
|
||||
'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField',
|
||||
|
|
|
@ -48,7 +48,9 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
|
|||
'lock/(?P<id>[1-9]\d*)/'
|
||||
=> 'PhabricatorProjectLockController',
|
||||
'members/(?P<id>[1-9]\d*)/'
|
||||
=> 'PhabricatorProjectMembersEditController',
|
||||
=> 'PhabricatorProjectMembersViewController',
|
||||
'members/(?P<id>[1-9]\d*)/add/'
|
||||
=> 'PhabricatorProjectMembersAddController',
|
||||
'members/(?P<id>[1-9]\d*)/remove/'
|
||||
=> 'PhabricatorProjectMembersRemoveController',
|
||||
'profile/(?P<id>[1-9]\d*)/'
|
||||
|
|
|
@ -27,7 +27,16 @@ final class PhabricatorProjectLockController
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$done_uri = $project->getURI();
|
||||
$done_uri = "/project/members/{$id}/";
|
||||
|
||||
if (!$project->supportsEditMembers()) {
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Membership Immutable'))
|
||||
->appendChild(
|
||||
pht('This project does not support editing membership.'))
|
||||
->addCancelButton($done_uri);
|
||||
}
|
||||
|
||||
$is_locked = $project->getIsMembershipLocked();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectMembersAddController
|
||||
extends PhabricatorProjectController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$project = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$project) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$this->setProject($project);
|
||||
|
||||
if (!$project->supportsEditMembers()) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$done_uri = "/project/members/{$id}/";
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$member_phids = $request->getArr('memberPHIDs');
|
||||
|
||||
$type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
|
||||
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||
->setMetadataValue('edge:type', $type_member)
|
||||
->setNewValue(
|
||||
array(
|
||||
'+' => array_fuse($member_phids),
|
||||
));
|
||||
|
||||
$editor = id(new PhabricatorProjectTransactionEditor($project))
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($project, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($done_uri);
|
||||
}
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setName('memberPHIDs')
|
||||
->setLabel(pht('Members'))
|
||||
->setDatasource(new PhabricatorPeopleDatasource()));
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Add Members'))
|
||||
->appendForm($form)
|
||||
->addCancelButton($done_uri)
|
||||
->addSubmitButton(pht('Add Members'));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectMembersEditController
|
||||
extends PhabricatorProjectController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$project = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->needMembers(true)
|
||||
->needImages(true)
|
||||
->executeOne();
|
||||
if (!$project) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$this->setProject($project);
|
||||
|
||||
$member_phids = $project->getMemberPHIDs();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$member_spec = array();
|
||||
|
||||
$remove = $request->getStr('remove');
|
||||
if ($remove) {
|
||||
$member_spec['-'] = array_fuse(array($remove));
|
||||
}
|
||||
|
||||
$add_members = $request->getArr('phids');
|
||||
if ($add_members) {
|
||||
$member_spec['+'] = array_fuse($add_members);
|
||||
}
|
||||
|
||||
$type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
|
||||
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||
->setMetadataValue('edge:type', $type_member)
|
||||
->setNewValue($member_spec);
|
||||
|
||||
$editor = id(new PhabricatorProjectTransactionEditor($project))
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($project, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($request->getRequestURI());
|
||||
}
|
||||
|
||||
$member_phids = array_reverse($member_phids);
|
||||
$handles = $this->loadViewerHandles($member_phids);
|
||||
|
||||
$state = array();
|
||||
foreach ($handles as $handle) {
|
||||
$state[] = array(
|
||||
'phid' => $handle->getPHID(),
|
||||
'name' => $handle->getFullName(),
|
||||
);
|
||||
}
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$supports_edit = $project->supportsEditMembers();
|
||||
|
||||
$form_box = null;
|
||||
$title = pht('Add Members');
|
||||
if ($can_edit && $supports_edit) {
|
||||
$header_name = pht('Edit Members');
|
||||
$view_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
|
||||
|
||||
$form = new AphrontFormView();
|
||||
$form
|
||||
->setUser($viewer)
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setName('phids')
|
||||
->setLabel(pht('Add Members'))
|
||||
->setDatasource(new PhabricatorPeopleDatasource()))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->addCancelButton($view_uri)
|
||||
->setValue(pht('Add Members')));
|
||||
$form_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText($title)
|
||||
->setForm($form);
|
||||
}
|
||||
|
||||
$member_list = $this->renderMemberList($project, $handles);
|
||||
|
||||
$nav = $this->getProfileMenu();
|
||||
$nav->selectFilter(PhabricatorProject::PANEL_MEMBERS);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Members'));
|
||||
|
||||
return $this->newPage()
|
||||
->setNavigation($nav)
|
||||
->setCrumbs($crumbs)
|
||||
->setTitle(array($project->getName(), $title))
|
||||
->appendChild($form_box)
|
||||
->appendChild($member_list);
|
||||
}
|
||||
|
||||
private function renderMemberList(
|
||||
PhabricatorProject $project,
|
||||
array $handles) {
|
||||
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$list = id(new PHUIObjectItemListView())
|
||||
->setNoDataString(pht('This project does not have any members.'));
|
||||
|
||||
foreach ($handles as $handle) {
|
||||
$remove_uri = $this->getApplicationURI(
|
||||
'/members/'.$project->getID().'/remove/?phid='.$handle->getPHID());
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setHeader($handle->getFullName())
|
||||
->setHref($handle->getURI())
|
||||
->setImageURI($handle->getImageURI());
|
||||
|
||||
if ($can_edit) {
|
||||
$item->addAction(
|
||||
id(new PHUIListItemView())
|
||||
->setIcon('fa-times')
|
||||
->setName(pht('Remove'))
|
||||
->setHref($remove_uri)
|
||||
->setWorkflow(true));
|
||||
}
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Members'))
|
||||
->setObjectList($list);
|
||||
|
||||
return $box;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectMembersViewController
|
||||
extends PhabricatorProjectController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$project = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->needMembers(true)
|
||||
->needWatchers(true)
|
||||
->needImages(true)
|
||||
->executeOne();
|
||||
if (!$project) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$this->setProject($project);
|
||||
$title = pht('Members and Watchers');
|
||||
|
||||
$properties = $this->buildProperties($project);
|
||||
$actions = $this->buildActions($project);
|
||||
$properties->setActionList($actions);
|
||||
|
||||
$object_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText($title)
|
||||
->addPropertyList($properties);
|
||||
|
||||
$member_list = id(new PhabricatorProjectMemberListView())
|
||||
->setUser($viewer)
|
||||
->setProject($project)
|
||||
->setUserPHIDs($project->getMemberPHIDs());
|
||||
|
||||
$watcher_list = id(new PhabricatorProjectWatcherListView())
|
||||
->setUser($viewer)
|
||||
->setProject($project)
|
||||
->setUserPHIDs($project->getWatcherPHIDs());
|
||||
|
||||
$nav = $this->getProfileMenu();
|
||||
$nav->selectFilter(PhabricatorProject::PANEL_MEMBERS);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Members'));
|
||||
|
||||
return $this->newPage()
|
||||
->setNavigation($nav)
|
||||
->setCrumbs($crumbs)
|
||||
->setTitle(array($project->getName(), $title))
|
||||
->appendChild(
|
||||
array(
|
||||
$object_box,
|
||||
$member_list,
|
||||
$watcher_list,
|
||||
));
|
||||
}
|
||||
|
||||
private function buildProperties(PhabricatorProject $project) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$view = id(new PHUIPropertyListView())
|
||||
->setUser($viewer)
|
||||
->setObject($project);
|
||||
|
||||
if ($project->isMilestone()) {
|
||||
$icon_key = PhabricatorProjectIconSet::getMilestoneIconKey();
|
||||
$icon = PhabricatorProjectIconSet::getIconIcon($icon_key);
|
||||
$target = PhabricatorProjectIconSet::getIconName($icon_key);
|
||||
$note = pht(
|
||||
'Members of the parent project are members of this project.');
|
||||
$show_join = false;
|
||||
} else if ($project->getHasSubprojects()) {
|
||||
$icon = 'fa-sitemap';
|
||||
$target = pht('Parent Project');
|
||||
$note = pht(
|
||||
'Members of all subprojects are members of this project.');
|
||||
$show_join = false;
|
||||
} else if ($project->getIsMembershipLocked()) {
|
||||
$icon = 'fa-lock';
|
||||
$target = pht('Locked Project');
|
||||
$note = pht(
|
||||
'Users with access may join this project, but may not leave.');
|
||||
$show_join = true;
|
||||
} else {
|
||||
$icon = 'fa-briefcase';
|
||||
$target = pht('Normal Project');
|
||||
$note = pht('Users with access may join and leave this project.');
|
||||
$show_join = true;
|
||||
}
|
||||
|
||||
$item = id(new PHUIStatusItemView())
|
||||
->setIcon($icon)
|
||||
->setTarget(phutil_tag('strong', array(), $target))
|
||||
->setNote($note);
|
||||
|
||||
$status = id(new PHUIStatusListView())
|
||||
->addItem($item);
|
||||
|
||||
$view->addProperty(pht('Membership'), $status);
|
||||
|
||||
if ($show_join) {
|
||||
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
|
||||
$viewer,
|
||||
$project);
|
||||
|
||||
$view->addProperty(
|
||||
pht('Joinable By'),
|
||||
$descriptions[PhabricatorPolicyCapability::CAN_JOIN]);
|
||||
}
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
private function buildActions(PhabricatorProject $project) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $project->getID();
|
||||
|
||||
$view = id(new PhabricatorActionListView())
|
||||
->setUser($viewer);
|
||||
|
||||
$is_locked = $project->getIsMembershipLocked();
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$supports_edit = $project->supportsEditMembers();
|
||||
|
||||
$can_join = $supports_edit && PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
PhabricatorPolicyCapability::CAN_JOIN);
|
||||
|
||||
$can_leave = $supports_edit && (!$is_locked || $can_edit);
|
||||
|
||||
if (!$project->isUserMember($viewer->getPHID())) {
|
||||
$view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref('/project/update/'.$project->getID().'/join/')
|
||||
->setIcon('fa-plus')
|
||||
->setDisabled(!$can_join)
|
||||
->setWorkflow(true)
|
||||
->setName(pht('Join Project')));
|
||||
} else {
|
||||
$view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref('/project/update/'.$project->getID().'/leave/')
|
||||
->setIcon('fa-times')
|
||||
->setDisabled(!$can_leave)
|
||||
->setWorkflow(true)
|
||||
->setName(pht('Leave Project')));
|
||||
|
||||
if (!$project->isUserWatcher($viewer->getPHID())) {
|
||||
$view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setWorkflow(true)
|
||||
->setHref('/project/watch/'.$project->getID().'/')
|
||||
->setIcon('fa-eye')
|
||||
->setName(pht('Watch Project')));
|
||||
} else {
|
||||
$view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setWorkflow(true)
|
||||
->setHref('/project/unwatch/'.$project->getID().'/')
|
||||
->setIcon('fa-eye-slash')
|
||||
->setName(pht('Unwatch Project')));
|
||||
}
|
||||
}
|
||||
|
||||
$can_add = $can_edit && $supports_edit;
|
||||
|
||||
$view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Add Members'))
|
||||
->setIcon('fa-user-plus')
|
||||
->setHref("/project/members/{$id}/add/")
|
||||
->setWorkflow(true)
|
||||
->setDisabled(!$can_add));
|
||||
|
||||
$can_lock = $can_edit && $supports_edit && $this->hasApplicationCapability(
|
||||
ProjectCanLockProjectsCapability::CAPABILITY);
|
||||
|
||||
if ($is_locked) {
|
||||
$lock_name = pht('Unlock Project');
|
||||
$lock_icon = 'fa-unlock';
|
||||
} else {
|
||||
$lock_name = pht('Lock Project');
|
||||
$lock_icon = 'fa-lock';
|
||||
}
|
||||
|
||||
$view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName($lock_name)
|
||||
->setIcon($lock_icon)
|
||||
->setHref($this->getApplicationURI("lock/{$id}/"))
|
||||
->setDisabled(!$can_lock)
|
||||
->setWorkflow(true));
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
}
|
|
@ -106,65 +106,6 @@ final class PhabricatorProjectProfileController
|
|||
->setWorkflow(true));
|
||||
}
|
||||
|
||||
$can_lock = $can_edit && $this->hasApplicationCapability(
|
||||
ProjectCanLockProjectsCapability::CAPABILITY);
|
||||
|
||||
if ($project->getIsMembershipLocked()) {
|
||||
$lock_name = pht('Unlock Project');
|
||||
$lock_icon = 'fa-unlock';
|
||||
} else {
|
||||
$lock_name = pht('Lock Project');
|
||||
$lock_icon = 'fa-lock';
|
||||
}
|
||||
|
||||
$view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName($lock_name)
|
||||
->setIcon($lock_icon)
|
||||
->setHref($this->getApplicationURI("lock/{$id}/"))
|
||||
->setDisabled(!$can_lock)
|
||||
->setWorkflow(true));
|
||||
|
||||
$action = null;
|
||||
if (!$project->isUserMember($viewer->getPHID())) {
|
||||
$can_join = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
PhabricatorPolicyCapability::CAN_JOIN);
|
||||
|
||||
$action = id(new PhabricatorActionView())
|
||||
->setUser($viewer)
|
||||
->setRenderAsForm(true)
|
||||
->setHref('/project/update/'.$project->getID().'/join/')
|
||||
->setIcon('fa-plus')
|
||||
->setDisabled(!$can_join)
|
||||
->setName(pht('Join Project'));
|
||||
$view->addAction($action);
|
||||
} else {
|
||||
$action = id(new PhabricatorActionView())
|
||||
->setWorkflow(true)
|
||||
->setHref('/project/update/'.$project->getID().'/leave/')
|
||||
->setIcon('fa-times')
|
||||
->setName(pht('Leave Project...'));
|
||||
$view->addAction($action);
|
||||
|
||||
if (!$project->isUserWatcher($viewer->getPHID())) {
|
||||
$action = id(new PhabricatorActionView())
|
||||
->setWorkflow(true)
|
||||
->setHref('/project/watch/'.$project->getID().'/')
|
||||
->setIcon('fa-eye')
|
||||
->setName(pht('Watch Project'));
|
||||
$view->addAction($action);
|
||||
} else {
|
||||
$action = id(new PhabricatorActionView())
|
||||
->setWorkflow(true)
|
||||
->setHref('/project/unwatch/'.$project->getID().'/')
|
||||
->setIcon('fa-eye-slash')
|
||||
->setName(pht('Unwatch Project'));
|
||||
$view->addAction($action);
|
||||
}
|
||||
}
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
|
@ -206,18 +147,10 @@ final class PhabricatorProjectProfileController
|
|||
->setAsInline(true)
|
||||
: phutil_tag('em', array(), pht('None')));
|
||||
|
||||
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
|
||||
$viewer,
|
||||
$project);
|
||||
|
||||
$view->addProperty(
|
||||
pht('Looks Like'),
|
||||
$viewer->renderHandle($project->getPHID())->setAsTag(true));
|
||||
|
||||
$view->addProperty(
|
||||
pht('Joinable By'),
|
||||
$descriptions[PhabricatorPolicyCapability::CAN_JOIN]);
|
||||
|
||||
$field_list = PhabricatorCustomField::getObjectFields(
|
||||
$project,
|
||||
PhabricatorCustomField::ROLE_VIEW);
|
||||
|
|
|
@ -12,14 +12,11 @@ final class PhabricatorProjectUpdateController
|
|||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
);
|
||||
|
||||
$process_action = false;
|
||||
switch ($action) {
|
||||
case 'join':
|
||||
$capabilities[] = PhabricatorPolicyCapability::CAN_JOIN;
|
||||
$process_action = $request->isFormPost();
|
||||
break;
|
||||
case 'leave':
|
||||
$process_action = $request->isDialogFormPost();
|
||||
break;
|
||||
default:
|
||||
return new Aphront404Response();
|
||||
|
@ -35,10 +32,13 @@ final class PhabricatorProjectUpdateController
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$project_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
|
||||
if (!$project->supportsEditMembers()) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
if ($process_action) {
|
||||
$done_uri = "/project/members/{$id}/";
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$edge_action = null;
|
||||
switch ($action) {
|
||||
case 'join':
|
||||
|
@ -50,6 +50,7 @@ final class PhabricatorProjectUpdateController
|
|||
}
|
||||
|
||||
$type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
|
||||
|
||||
$member_spec = array(
|
||||
$edge_action => array($viewer->getPHID() => $viewer->getPHID()),
|
||||
);
|
||||
|
@ -67,46 +68,47 @@ final class PhabricatorProjectUpdateController
|
|||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($project, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($project_uri);
|
||||
return id(new AphrontRedirectResponse())->setURI($done_uri);
|
||||
}
|
||||
|
||||
$dialog = null;
|
||||
switch ($action) {
|
||||
case 'leave':
|
||||
$dialog = new AphrontDialogView();
|
||||
$dialog->setUser($viewer);
|
||||
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);
|
||||
break;
|
||||
default:
|
||||
return new Aphront404Response();
|
||||
$is_locked = $project->getIsMembershipLocked();
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
$can_leave = ($can_edit || !$is_locked);
|
||||
|
||||
$button = null;
|
||||
if ($action == 'leave') {
|
||||
if ($can_leave) {
|
||||
$title = pht('Leave Project');
|
||||
$body = pht(
|
||||
'Your tremendous contributions to this project will be sorely '.
|
||||
'missed. Are you sure you want to leave?');
|
||||
$button = pht('Leave Project');
|
||||
} else {
|
||||
$title = pht('Membership Locked');
|
||||
$body = pht(
|
||||
'Membership for this project is locked. You can not leave.');
|
||||
}
|
||||
} else {
|
||||
$title = pht('Join Project');
|
||||
$body = pht(
|
||||
'Join this project? You will become a member and enjoy whatever '.
|
||||
'benefits membership may confer.');
|
||||
$button = pht('Join Project');
|
||||
}
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
$dialog = $this->newDialog()
|
||||
->setTitle($title)
|
||||
->appendParagraph($body)
|
||||
->addCancelButton($done_uri);
|
||||
|
||||
if ($button) {
|
||||
$dialog->addSubmitButton($button);
|
||||
}
|
||||
|
||||
return $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) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
return
|
||||
$project->getIsMembershipLocked() &&
|
||||
!PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,9 @@ final class PhabricatorProjectWatchController
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$project_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
|
||||
$done_uri = "/project/members/{$id}/";
|
||||
|
||||
// You must be a member of a project to
|
||||
// You must be a member of a project to watch it.
|
||||
if (!$project->isUserMember($viewer->getPHID())) {
|
||||
return new Aphront400Response();
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ final class PhabricatorProjectWatchController
|
|||
->setContinueOnMissingFields(true)
|
||||
->applyTransactions($project, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($project_uri);
|
||||
return id(new AphrontRedirectResponse())->setURI($done_uri);
|
||||
}
|
||||
|
||||
$dialog = null;
|
||||
|
@ -83,7 +83,7 @@ final class PhabricatorProjectWatchController
|
|||
return $this->newDialog()
|
||||
->setTitle($title)
|
||||
->appendParagraph($body)
|
||||
->addCancelButton($project_uri)
|
||||
->addCancelButton($done_uri)
|
||||
->addSubmitButton($submit);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectMemberListView
|
||||
extends PhabricatorProjectUserListView {
|
||||
|
||||
protected function canEditList() {
|
||||
$viewer = $this->getUser();
|
||||
$project = $this->getProject();
|
||||
|
||||
if (!$project->supportsEditMembers()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$project,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
}
|
||||
|
||||
protected function getNoDataString() {
|
||||
return pht('This project does not have any members.');
|
||||
}
|
||||
|
||||
protected function getRemoveURI($phid) {
|
||||
$project = $this->getProject();
|
||||
$id = $project->getID();
|
||||
return "/project/members/{$id}/remove/?phid={$phid}";
|
||||
}
|
||||
|
||||
protected function getHeaderText() {
|
||||
return pht('Members');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorProjectUserListView extends AphrontView {
|
||||
|
||||
private $project;
|
||||
private $userPHIDs;
|
||||
|
||||
public function setProject(PhabricatorProject $project) {
|
||||
$this->project = $project;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProject() {
|
||||
return $this->project;
|
||||
}
|
||||
|
||||
public function setUserPHIDs(array $user_phids) {
|
||||
$this->userPHIDs = $user_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUserPHIDs() {
|
||||
return $this->userPHIDs;
|
||||
}
|
||||
|
||||
abstract protected function canEditList();
|
||||
abstract protected function getNoDataString();
|
||||
abstract protected function getRemoveURI($phid);
|
||||
abstract protected function getHeaderText();
|
||||
|
||||
public function render() {
|
||||
$viewer = $this->getUser();
|
||||
$project = $this->getProject();
|
||||
$user_phids = $this->getUserPHIDs();
|
||||
|
||||
$can_edit = $this->canEditList();
|
||||
$no_data = $this->getNoDataString();
|
||||
|
||||
$list = id(new PHUIObjectItemListView())
|
||||
->setNoDataString($no_data);
|
||||
|
||||
$user_phids = array_reverse($user_phids);
|
||||
$handles = $viewer->loadHandles($user_phids);
|
||||
|
||||
// Always put the viewer first if they are on the list.
|
||||
$user_phids = array_fuse($user_phids);
|
||||
$user_phids =
|
||||
array_select_keys($user_phids, array($viewer->getPHID())) +
|
||||
$user_phids;
|
||||
|
||||
foreach ($user_phids as $user_phid) {
|
||||
$handle = $handles[$user_phid];
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setHeader($handle->getFullName())
|
||||
->setHref($handle->getURI())
|
||||
->setImageURI($handle->getImageURI());
|
||||
|
||||
if ($can_edit) {
|
||||
$remove_uri = $this->getRemoveURI($user_phid);
|
||||
|
||||
$item->addAction(
|
||||
id(new PHUIListItemView())
|
||||
->setIcon('fa-times')
|
||||
->setName(pht('Remove'))
|
||||
->setHref($remove_uri)
|
||||
->setWorkflow(true));
|
||||
}
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeaderText($this->getHeaderText())
|
||||
->setObjectList($list);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectWatcherListView
|
||||
extends PhabricatorProjectUserListView {
|
||||
|
||||
protected function canEditList() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getNoDataString() {
|
||||
return pht('This project does not have any watchers.');
|
||||
}
|
||||
|
||||
protected function getRemoveURI($phid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getHeaderText() {
|
||||
return pht('Watchers');
|
||||
}
|
||||
|
||||
}
|
|
@ -24,7 +24,6 @@ final class PHUIStatusItemView extends AphrontTagView {
|
|||
const ICON_CLOCK = 'fa-clock-o';
|
||||
const ICON_STAR = 'fa-star';
|
||||
|
||||
/* render_textarea */
|
||||
public function setIcon($icon, $color = null, $label = null) {
|
||||
$this->icon = $icon;
|
||||
$this->iconLabel = $label;
|
||||
|
|
|
@ -73,21 +73,24 @@
|
|||
background-size: 100%;
|
||||
}
|
||||
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phui-list-item-href {
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
|
||||
.phui-list-item-href {
|
||||
text-align: center;
|
||||
padding: 42px 8px 12px;
|
||||
font-size: 11px;
|
||||
line-height: 13px;
|
||||
}
|
||||
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phui-list-item-name {
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
|
||||
.phui-list-item-name {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phui-list-item-icon,
|
||||
.phui-profile-menu .phui-profile-menu-collapsed
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
|
||||
.phui-list-item-icon,
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
|
||||
.phui-list-item-href .phui-icon-view {
|
||||
top: 10px;
|
||||
left: 29px;
|
||||
|
@ -166,27 +169,31 @@
|
|||
left: 120px;
|
||||
}
|
||||
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer {
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
|
||||
.phui-profile-menu-footer {
|
||||
width: 40px;
|
||||
height: {$menu.profile.item.height};
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer-1 {
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
|
||||
.phui-profile-menu-footer-1 {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer-2 {
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
|
||||
.phui-profile-menu-footer-2 {
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
|
||||
.phui-profile-menu-footer
|
||||
.phui-list-item-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer
|
||||
.phui-profile-menu .phui-profile-menu-collapsed .phabricator-side-menu
|
||||
.phui-profile-menu-footer
|
||||
.phui-list-item-icon {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
|
|
Loading…
Reference in a new issue