Page MenuHomePhabricator

D15059.diff
No OneTemporary

D15059.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -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 @@
'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 @@
'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',
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
@@ -2895,12 +2895,14 @@
'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 @@
'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 @@
'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 @@
'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener',
'PhabricatorProjectUpdateController' => 'PhabricatorProjectController',
'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
+ 'PhabricatorProjectUserListView' => 'AphrontView',
'PhabricatorProjectViewController' => 'PhabricatorProjectController',
'PhabricatorProjectWatchController' => 'PhabricatorProjectController',
+ 'PhabricatorProjectWatcherListView' => 'PhabricatorProjectUserListView',
'PhabricatorProjectWorkboardProfilePanel' => 'PhabricatorProfilePanel',
'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField',
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
@@ -48,7 +48,9 @@
'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*)/'
diff --git a/src/applications/project/controller/PhabricatorProjectLockController.php b/src/applications/project/controller/PhabricatorProjectLockController.php
--- a/src/applications/project/controller/PhabricatorProjectLockController.php
+++ b/src/applications/project/controller/PhabricatorProjectLockController.php
@@ -27,7 +27,16 @@
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()) {
diff --git a/src/applications/project/controller/PhabricatorProjectMembersAddController.php b/src/applications/project/controller/PhabricatorProjectMembersAddController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/project/controller/PhabricatorProjectMembersAddController.php
@@ -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'));
+ }
+
+}
diff --git a/src/applications/project/controller/PhabricatorProjectMembersEditController.php b/src/applications/project/controller/PhabricatorProjectMembersEditController.php
deleted file mode 100644
--- a/src/applications/project/controller/PhabricatorProjectMembersEditController.php
+++ /dev/null
@@ -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;
- }
-}
diff --git a/src/applications/project/controller/PhabricatorProjectMembersViewController.php b/src/applications/project/controller/PhabricatorProjectMembersViewController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/project/controller/PhabricatorProjectMembersViewController.php
@@ -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;
+ }
+
+}
diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php
--- a/src/applications/project/controller/PhabricatorProjectProfileController.php
+++ b/src/applications/project/controller/PhabricatorProjectProfileController.php
@@ -106,65 +106,6 @@
->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 @@
->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);
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
@@ -12,14 +12,11 @@
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 @@
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 @@
}
$type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
+
$member_spec = array(
$edge_action => array($viewer->getPHID() => $viewer->getPHID()),
);
@@ -67,46 +68,47 @@
->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);
- /**
- * 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);
+ if ($button) {
+ $dialog->addSubmitButton($button);
+ }
+
+ return $dialog;
}
+
}
diff --git a/src/applications/project/controller/PhabricatorProjectWatchController.php b/src/applications/project/controller/PhabricatorProjectWatchController.php
--- a/src/applications/project/controller/PhabricatorProjectWatchController.php
+++ b/src/applications/project/controller/PhabricatorProjectWatchController.php
@@ -18,9 +18,9 @@
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 @@
->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 @@
return $this->newDialog()
->setTitle($title)
->appendParagraph($body)
- ->addCancelButton($project_uri)
+ ->addCancelButton($done_uri)
->addSubmitButton($submit);
}
diff --git a/src/applications/project/view/PhabricatorProjectMemberListView.php b/src/applications/project/view/PhabricatorProjectMemberListView.php
new file mode 100644
--- /dev/null
+++ b/src/applications/project/view/PhabricatorProjectMemberListView.php
@@ -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');
+ }
+
+}
diff --git a/src/applications/project/view/PhabricatorProjectUserListView.php b/src/applications/project/view/PhabricatorProjectUserListView.php
new file mode 100644
--- /dev/null
+++ b/src/applications/project/view/PhabricatorProjectUserListView.php
@@ -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);
+ }
+
+}
diff --git a/src/applications/project/view/PhabricatorProjectWatcherListView.php b/src/applications/project/view/PhabricatorProjectWatcherListView.php
new file mode 100644
--- /dev/null
+++ b/src/applications/project/view/PhabricatorProjectWatcherListView.php
@@ -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');
+ }
+
+}
diff --git a/src/view/phui/PHUIStatusItemView.php b/src/view/phui/PHUIStatusItemView.php
--- a/src/view/phui/PHUIStatusItemView.php
+++ b/src/view/phui/PHUIStatusItemView.php
@@ -24,7 +24,6 @@
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;
diff --git a/webroot/rsrc/css/phui/phui-profile-menu.css b/webroot/rsrc/css/phui/phui-profile-menu.css
--- a/webroot/rsrc/css/phui/phui-profile-menu.css
+++ b/webroot/rsrc/css/phui/phui-profile-menu.css
@@ -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;

File Metadata

Mime Type
text/plain
Expires
Wed, Feb 5, 9:53 AM (21 h, 13 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7092038
Default Alt Text
D15059.diff (36 KB)

Event Timeline