Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2896176
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
43 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/project/constants/PhabricatorProjectTransactionType.php b/src/applications/project/constants/PhabricatorProjectTransactionType.php
index 49ef0a9d81..77170e6f0f 100644
--- a/src/applications/project/constants/PhabricatorProjectTransactionType.php
+++ b/src/applications/project/constants/PhabricatorProjectTransactionType.php
@@ -1,26 +1,29 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorProjectTransactionType
extends PhabricatorProjectConstants {
const TYPE_NAME = 'name';
const TYPE_MEMBERS = 'members';
const TYPE_STATUS = 'status';
+ const TYPE_CAN_VIEW = 'canview';
+ const TYPE_CAN_EDIT = 'canedit';
+ const TYPE_CAN_JOIN = 'canjoin';
}
diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php
index 5b404a152b..2b7736ab74 100644
--- a/src/applications/project/controller/PhabricatorProjectController.php
+++ b/src/applications/project/controller/PhabricatorProjectController.php
@@ -1,63 +1,82 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
abstract class PhabricatorProjectController extends PhabricatorController {
public function buildStandardPageResponse($view, array $data) {
$page = $this->buildStandardPageView();
$page->setApplicationName('Project');
$page->setBaseURI('/project/');
$page->setTitle(idx($data, 'title'));
$page->setGlyph("\xE2\x98\xA3");
$page->appendChild($view);
$response = new AphrontWebpageResponse();
return $response->setContent($page->render());
}
protected function buildLocalNavigation(PhabricatorProject $project) {
$id = $project->getID();
$nav_view = new AphrontSideNavFilterView();
$uri = new PhutilURI('/project/view/'.$id.'/');
$nav_view->setBaseURI($uri);
$external_arrow = "\xE2\x86\x97";
$tasks_uri = '/maniphest/view/all/?projects='.$project->getPHID();
$slug = PhabricatorSlug::normalize($project->getName());
$phriction_uri = '/w/projects/'.$slug;
$edit_uri = '/project/edit/'.$id.'/';
$members_uri = '/project/members/'.$id.'/';
$nav_view->addFilter('dashboard', 'Dashboard');
$nav_view->addSpacer();
$nav_view->addFilter('feed', 'Feed');
$nav_view->addFilter(null, 'Tasks '.$external_arrow, $tasks_uri);
$nav_view->addFilter(null, 'Wiki '.$external_arrow, $phriction_uri);
$nav_view->addFilter('people', 'People');
$nav_view->addFilter('about', 'About');
+
+ $user = $this->getRequest()->getUser();
+ $can_edit = PhabricatorPolicyCapability::CAN_EDIT;
+
$nav_view->addSpacer();
- $nav_view->addFilter('edit', "Edit Project\xE2\x80\xA6", $edit_uri);
- $nav_view->addFilter('members', "Edit Members\xE2\x80\xA6", $members_uri);
+ if (PhabricatorPolicyFilter::hasCapability($user, $project, $can_edit)) {
+ $nav_view->addFilter('edit', "Edit Project\xE2\x80\xA6", $edit_uri);
+ $nav_view->addFilter('members', "Edit Members\xE2\x80\xA6", $members_uri);
+ } else {
+ $nav_view->addFilter(
+ 'edit',
+ "Edit Project\xE2\x80\xA6",
+ $edit_uri,
+ $relative = false,
+ 'disabled');
+ $nav_view->addFilter(
+ 'members',
+ "Edit Members\xE2\x80\xA6",
+ $members_uri,
+ $relative = false,
+ 'disabled');
+ }
return $nav_view;
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectMembersEditController.php b/src/applications/project/controller/PhabricatorProjectMembersEditController.php
index 02f9a1239f..5e3aad4861 100644
--- a/src/applications/project/controller/PhabricatorProjectMembersEditController.php
+++ b/src/applications/project/controller/PhabricatorProjectMembersEditController.php
@@ -1,172 +1,180 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorProjectMembersEditController
extends PhabricatorProjectController {
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
- $project = id(new PhabricatorProject())->load($this->id);
+ $project = id(new PhabricatorProjectQuery())
+ ->setViewer($user)
+ ->withIDs(array($this->id))
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ))
+ ->executeOne();
if (!$project) {
return new Aphront404Response();
}
$profile = $project->loadProfile();
if (empty($profile)) {
$profile = new PhabricatorProjectProfile();
}
$member_phids = $project->loadMemberPHIDs();
$errors = array();
if ($request->isFormPost()) {
$changed_something = false;
$member_map = array_fill_keys($member_phids, true);
$remove = $request->getStr('remove');
if ($remove) {
if (isset($member_map[$remove])) {
unset($member_map[$remove]);
$changed_something = true;
}
} else {
$new_members = $request->getArr('phids');
foreach ($new_members as $member) {
if (empty($member_map[$member])) {
$member_map[$member] = true;
$changed_something = true;
}
}
}
$xactions = array();
if ($changed_something) {
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType(
PhabricatorProjectTransactionType::TYPE_MEMBERS);
$xaction->setNewValue(array_keys($member_map));
$xactions[] = $xaction;
}
if ($xactions) {
$editor = new PhabricatorProjectEditor($project);
$editor->setUser($user);
$editor->applyTransactions($xactions);
}
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI());
}
$member_phids = array_reverse($member_phids);
$handles = id(new PhabricatorObjectHandleData($member_phids))
->loadHandles();
$state = array();
foreach ($handles as $handle) {
$state[] = array(
'phid' => $handle->getPHID(),
'name' => $handle->getFullName(),
);
}
$header_name = 'Edit Members';
$title = 'Edit Members';
$list = $this->renderMemberList($handles);
$form = new AphrontFormView();
$form
->setUser($user)
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('phids')
->setLabel('Add Members')
->setDatasource('/typeahead/common/users/'))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton('/project/view/'.$project->getID().'/')
->setValue('Add Members'));
$faux_form = id(new AphrontFormLayoutView())
->setBackgroundShading(true)
->setPadded(true)
->appendChild(
id(new AphrontFormInsetView())
->setTitle('Current Members ('.count($handles).')')
->appendChild($list));
$panel = new AphrontPanelView();
$panel->setHeader($header_name);
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->appendChild($form);
$panel->appendChild('<br />');
$panel->appendChild($faux_form);
$nav = $this->buildLocalNavigation($project);
$nav->selectFilter('members');
$nav->appendChild($panel);
return $this->buildStandardPageResponse(
$nav,
array(
'title' => $title,
));
}
private function renderMemberList(array $handles) {
$request = $this->getRequest();
$user = $request->getUser();
$list = id(new PhabricatorObjectListView())
->setHandles($handles);
foreach ($handles as $handle) {
$hidden_input = phutil_render_tag(
'input',
array(
'type' => 'hidden',
'name' => 'remove',
'value' => $handle->getPHID(),
),
'');
$button = javelin_render_tag(
'button',
array(
'class' => 'grey',
),
pht('Remove'));
$list->addButton(
$handle,
phabricator_render_form(
$user,
array(
'method' => 'POST',
'action' => $request->getRequestURI(),
),
$hidden_input.$button));
}
return $list;
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php
index 608cbbe4ab..241cb69518 100644
--- a/src/applications/project/controller/PhabricatorProjectProfileController.php
+++ b/src/applications/project/controller/PhabricatorProjectProfileController.php
@@ -1,291 +1,305 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorProjectProfileController
extends PhabricatorProjectController {
private $id;
private $page;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
$this->page = idx($data, 'page');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
- $project = id(new PhabricatorProject())->load($this->id);
+ $query = id(new PhabricatorProjectQuery())
+ ->setViewer($user)
+ ->withIDs(array($this->id));
+
+ if ($this->page == 'people') {
+ $query->needMembers(true);
+ }
+
+ $project = $query->executeOne();
if (!$project) {
return new Aphront404Response();
}
+
$profile = $project->loadProfile();
if (!$profile) {
$profile = new PhabricatorProjectProfile();
}
$picture = $profile->loadProfileImageURI();
- $members = $project->loadMemberPHIDs();
- $member_map = array_fill_keys($members, true);
$nav_view = $this->buildLocalNavigation($project);
$this->page = $nav_view->selectFilter($this->page, 'dashboard');
-
require_celerity_resource('phabricator-profile-css');
switch ($this->page) {
case 'dashboard':
$content = $this->renderTasksPage($project, $profile);
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(
array(
$project->getPHID(),
));
$query->setLimit(50);
$query->setViewer($this->getRequest()->getUser());
$stories = $query->execute();
$content .= $this->renderStories($stories);
break;
case 'about':
$content = $this->renderAboutPage($project, $profile);
break;
case 'people':
$content = $this->renderPeoplePage($project, $profile);
break;
case 'feed':
$content = $this->renderFeedPage($project, $profile);
break;
default:
throw new Exception("Unimplemented filter '{$this->page}'.");
}
$content = '<div style="padding: 1em;">'.$content.'</div>';
$nav_view->appendChild($content);
$header = new PhabricatorProfileHeaderView();
$header->setName($project->getName());
$header->setDescription(
phutil_utf8_shorten($profile->getBlurb(), 1024));
$header->setProfilePicture($picture);
$action = null;
- if (empty($member_map[$user->getPHID()])) {
+ if (!$project->isUserMember($user->getPHID())) {
+ $can_join = PhabricatorPolicyCapability::CAN_JOIN;
+
+ if (PhabricatorPolicyFilter::hasCapability($user, $project, $can_join)) {
+ $class = 'green';
+ } else {
+ $class = 'grey disabled';
+ }
+
$action = phabricator_render_form(
$user,
array(
'action' => '/project/update/'.$project->getID().'/join/',
'method' => 'post',
),
phutil_render_tag(
'button',
array(
- 'class' => 'green',
+ 'class' => $class,
),
'Join Project'));
} else {
$action = javelin_render_tag(
'a',
array(
'href' => '/project/update/'.$project->getID().'/leave/',
'sigil' => 'workflow',
'class' => 'grey button',
),
'Leave Project...');
}
$header->addAction($action);
$header->appendChild($nav_view);
return $this->buildStandardPageResponse(
$header,
array(
'title' => $project->getName().' Project',
));
}
private function renderAboutPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$viewer = $this->getRequest()->getUser();
$blurb = $profile->getBlurb();
$blurb = phutil_escape_html($blurb);
$blurb = str_replace("\n", '<br />', $blurb);
$phids = array($project->getAuthorPHID());
$phids = array_unique($phids);
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
$timestamp = phabricator_datetime($project->getDateCreated(), $viewer);
$about =
'<div class="phabricator-profile-info-group">
<h1 class="phabricator-profile-info-header">About</h1>
<div class="phabricator-profile-info-pane">
<table class="phabricator-profile-info-table">
<tr>
<th>Creator</th>
<td>'.$handles[$project->getAuthorPHID()]->renderLink().'</td>
</tr>
<tr>
<th>Created</th>
<td>'.$timestamp.'</td>
</tr>
<tr>
<th>PHID</th>
<td>'.phutil_escape_html($project->getPHID()).'</td>
</tr>
<tr>
<th>Blurb</th>
<td>'.$blurb.'</td>
</tr>
</table>
</div>
</div>';
return $about;
}
private function renderPeoplePage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
- $member_phids = $project->loadMemberPHIDs();
+ $member_phids = $project->getMemberPHIDs();
$handles = id(new PhabricatorObjectHandleData($member_phids))
->loadHandles();
$affiliated = array();
foreach ($handles as $phids => $handle) {
$affiliated[] = '<li>'.$handle->renderLink().'</li>';
}
if ($affiliated) {
$affiliated = '<ul>'.implode("\n", $affiliated).'</ul>';
} else {
$affiliated = '<p><em>No one is affiliated with this project.</em></p>';
}
return
'<div class="phabricator-profile-info-group">'.
'<h1 class="phabricator-profile-info-header">People</h1>'.
'<div class="phabricator-profile-info-pane">'.
$affiliated.
'</div>'.
'</div>';
}
private function renderFeedPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(array($project->getPHID()));
$query->setViewer($this->getRequest()->getUser());
$query->setLimit(100);
$stories = $query->execute();
if (!$stories) {
return 'There are no stories about this project.';
}
return $this->renderStories($stories);
}
private function renderStories(array $stories) {
assert_instances_of($stories, 'PhabricatorFeedStory');
$builder = new PhabricatorFeedBuilder($stories);
$builder->setUser($this->getRequest()->getUser());
$view = $builder->buildView();
return
'<div class="phabricator-profile-info-group">'.
'<h1 class="phabricator-profile-info-header">Activity Feed</h1>'.
'<div class="phabricator-profile-info-pane">'.
$view->render().
'</div>'.
'</div>';
}
private function renderTasksPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$query = id(new ManiphestTaskQuery())
->withProjects(array($project->getPHID()))
->withStatus(ManiphestTaskQuery::STATUS_OPEN)
->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY)
->setLimit(10)
->setCalculateRows(true);
$tasks = $query->execute();
$count = $query->getRowCount();
$phids = mpull($tasks, 'getOwnerPHID');
$phids = array_filter($phids);
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
$task_views = array();
foreach ($tasks as $task) {
$view = id(new ManiphestTaskSummaryView())
->setTask($task)
->setHandles($handles)
->setUser($this->getRequest()->getUser());
$task_views[] = $view->render();
}
if (empty($tasks)) {
$task_views = '<em>No open tasks.</em>';
} else {
$task_views = implode('', $task_views);
}
$open = number_format($count);
$more_link = phutil_render_tag(
'a',
array(
'href' => '/maniphest/view/all/?projects='.$project->getPHID(),
),
"View All Open Tasks \xC2\xBB");
$content =
'<div class="phabricator-profile-info-group">
<h1 class="phabricator-profile-info-header">'.
"Open Tasks ({$open})".
'</h1>'.
'<div class="phabricator-profile-info-pane">'.
$task_views.
'<div class="phabricator-profile-info-pane-more-link">'.
$more_link.
'</div>'.
'</div>
</div>';
return $content;
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectProfileEditController.php b/src/applications/project/controller/PhabricatorProjectProfileEditController.php
index f84ae0d2f3..b2839fa9b8 100644
--- a/src/applications/project/controller/PhabricatorProjectProfileEditController.php
+++ b/src/applications/project/controller/PhabricatorProjectProfileEditController.php
@@ -1,192 +1,244 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorProjectProfileEditController
extends PhabricatorProjectController {
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
- $project = id(new PhabricatorProject())->load($this->id);
+ $project = id(new PhabricatorProjectQuery())
+ ->setViewer($user)
+ ->withIDs(array($this->id))
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ))
+ ->executeOne();
if (!$project) {
return new Aphront404Response();
}
+
$profile = $project->loadProfile();
if (empty($profile)) {
$profile = new PhabricatorProjectProfile();
}
$img_src = $profile->loadProfileImageURI();
$options = PhabricatorProjectStatus::getStatusMap();
$supported_formats = PhabricatorFile::getTransformableImageFormats();
$e_name = true;
$e_image = null;
$errors = array();
if ($request->isFormPost()) {
try {
$xactions = array();
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType(
PhabricatorProjectTransactionType::TYPE_NAME);
$xaction->setNewValue($request->getStr('name'));
$xactions[] = $xaction;
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType(
PhabricatorProjectTransactionType::TYPE_STATUS);
$xaction->setNewValue($request->getStr('status'));
$xactions[] = $xaction;
+ $xaction = new PhabricatorProjectTransaction();
+ $xaction->setTransactionType(
+ PhabricatorProjectTransactionType::TYPE_CAN_VIEW);
+ $xaction->setNewValue($request->getStr('can_view'));
+ $xactions[] = $xaction;
+
+ $xaction = new PhabricatorProjectTransaction();
+ $xaction->setTransactionType(
+ PhabricatorProjectTransactionType::TYPE_CAN_EDIT);
+ $xaction->setNewValue($request->getStr('can_edit'));
+ $xactions[] = $xaction;
+
+ $xaction = new PhabricatorProjectTransaction();
+ $xaction->setTransactionType(
+ PhabricatorProjectTransactionType::TYPE_CAN_JOIN);
+ $xaction->setNewValue($request->getStr('can_join'));
+ $xactions[] = $xaction;
+
$editor = new PhabricatorProjectEditor($project);
$editor->setUser($user);
$editor->applyTransactions($xactions);
} catch (PhabricatorProjectNameCollisionException $ex) {
$e_name = 'Not Unique';
$errors[] = $ex->getMessage();
}
$profile->setBlurb($request->getStr('blurb'));
if (!strlen($project->getName())) {
$e_name = 'Required';
$errors[] = 'Project name is required.';
} else {
$e_name = null;
}
$default_image = $request->getExists('default_image');
if ($default_image) {
$profile->setProfileImagePHID(null);
} else if (!empty($_FILES['image'])) {
$err = idx($_FILES['image'], 'error');
if ($err != UPLOAD_ERR_NO_FILE) {
$file = PhabricatorFile::newFromPHPUpload(
$_FILES['image'],
array(
'authorPHID' => $user->getPHID(),
));
$okay = $file->isTransformableImage();
if ($okay) {
$xformer = new PhabricatorImageTransformer();
$xformed = $xformer->executeThumbTransform(
$file,
$x = 50,
$y = 50);
$profile->setProfileImagePHID($xformed->getPHID());
} else {
$e_image = 'Not Supported';
$errors[] =
'This server only supports these image formats: '.
implode(', ', $supported_formats).'.';
}
}
}
if (!$errors) {
$project->save();
$profile->setProjectPHID($project->getPHID());
$profile->save();
return id(new AphrontRedirectResponse())
->setURI('/project/view/'.$project->getID().'/');
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle('Form Errors');
$error_view->setErrors($errors);
}
$header_name = 'Edit Project';
$title = 'Edit Project';
$action = '/project/edit/'.$project->getID().'/';
$form = new AphrontFormView();
$form
->setID('project-edit-form')
->setUser($user)
->setAction($action)
->setEncType('multipart/form-data')
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Name')
->setName('name')
->setValue($project->getName())
->setError($e_name))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Project Status')
->setName('status')
->setOptions($options)
->setValue($project->getStatus()))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel('Blurb')
->setName('blurb')
->setValue($profile->getBlurb()))
+ ->appendChild(
+ '<p class="aphront-form-instructions">NOTE: Policy settings are not '.
+ 'yet fully implemented. Some interfaces still ignore these settings, '.
+ 'particularly "Visible To".</p>')
+ ->appendChild(
+ id(new AphrontFormPolicyControl())
+ ->setUser($user)
+ ->setName('can_view')
+ ->setCaption('Members can always view a project.')
+ ->setPolicyObject($project)
+ ->setCapability(PhabricatorPolicyCapability::CAN_VIEW))
+ ->appendChild(
+ id(new AphrontFormPolicyControl())
+ ->setUser($user)
+ ->setName('can_edit')
+ ->setPolicyObject($project)
+ ->setCapability(PhabricatorPolicyCapability::CAN_EDIT))
+ ->appendChild(
+ id(new AphrontFormPolicyControl())
+ ->setUser($user)
+ ->setName('can_join')
+ ->setCaption(
+ 'Users who can edit a project can always join a project.')
+ ->setPolicyObject($project)
+ ->setCapability(PhabricatorPolicyCapability::CAN_JOIN))
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel('Profile Image')
->setValue(
phutil_render_tag(
'img',
array(
'src' => $img_src,
))))
->appendChild(
id(new AphrontFormImageControl())
->setLabel('Change Image')
->setName('image')
->setError($e_image)
->setCaption('Supported formats: '.implode(', ', $supported_formats)))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton('/project/view/'.$project->getID().'/')
->setValue('Save'));
$panel = new AphrontPanelView();
$panel->setHeader($header_name);
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->appendChild($form);
$nav = $this->buildLocalNavigation($project);
$nav->selectFilter('edit');
$nav->appendChild(
array(
$error_view,
$panel,
));
return $this->buildStandardPageResponse(
$nav,
array(
'title' => $title,
));
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectUpdateController.php b/src/applications/project/controller/PhabricatorProjectUpdateController.php
index b12bcc8fa7..41d1685f80 100644
--- a/src/applications/project/controller/PhabricatorProjectUpdateController.php
+++ b/src/applications/project/controller/PhabricatorProjectUpdateController.php
@@ -1,88 +1,94 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorProjectUpdateController
extends PhabricatorProjectController {
private $id;
private $action;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
$this->action = $data['action'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
- $project = id(new PhabricatorProjectQuery())
- ->setViewer($user)
- ->needMembers(true)
- ->withIDs(array($this->id))
- ->executeOne();
- if (!$project) {
- return new Aphront404Response();
- }
+ $capabilities = array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ );
$process_action = false;
switch ($this->action) {
case 'join':
+ $capabilities[] = PhabricatorPolicyCapability::CAN_JOIN;
$process_action = $request->isFormPost();
break;
case 'leave':
$process_action = $request->isDialogFormPost();
break;
default:
return new Aphront404Response();
}
+ $project = id(new PhabricatorProjectQuery())
+ ->setViewer($user)
+ ->withIDs(array($this->id))
+ ->needMembers(true)
+ ->requireCapabilities($capabilities)
+ ->executeOne();
+ if (!$project) {
+ return new Aphront404Response();
+ }
+
$project_uri = '/project/view/'.$project->getID().'/';
if ($process_action) {
switch ($this->action) {
case 'join':
PhabricatorProjectEditor::applyJoinProject($project, $user);
break;
case 'leave':
PhabricatorProjectEditor::applyLeaveProject($project, $user);
break;
}
return id(new AphrontRedirectResponse())->setURI($project_uri);
}
$dialog = null;
switch ($this->action) {
case 'leave':
$dialog = new AphrontDialogView();
$dialog->setUser($user);
$dialog->setTitle('Really leave project?');
$dialog->appendChild(
'<p>Your tremendous contributions to this project will be sorely '.
'missed. Are you sure you want to leave?</p>');
$dialog->addCancelButton($project_uri);
$dialog->addSubmitButton('Leave Project');
break;
default:
return new Aphront404Response();
}
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
diff --git a/src/applications/project/editor/PhabricatorProjectEditor.php b/src/applications/project/editor/PhabricatorProjectEditor.php
index 4c8646d9c6..3282520aa4 100644
--- a/src/applications/project/editor/PhabricatorProjectEditor.php
+++ b/src/applications/project/editor/PhabricatorProjectEditor.php
@@ -1,267 +1,394 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorProjectEditor {
private $project;
private $user;
private $projectName;
private $addEdges = array();
private $remEdges = array();
public static function applyJoinProject(
PhabricatorProject $project,
PhabricatorUser $user) {
$members = $project->getMemberPHIDs();
$members[] = $user->getPHID();
self::applyOneTransaction(
$project,
$user,
PhabricatorProjectTransactionType::TYPE_MEMBERS,
$members);
}
public static function applyLeaveProject(
PhabricatorProject $project,
PhabricatorUser $user) {
$members = array_fill_keys($project->getMemberPHIDs(), true);
unset($members[$user->getPHID()]);
$members = array_keys($members);
self::applyOneTransaction(
$project,
$user,
PhabricatorProjectTransactionType::TYPE_MEMBERS,
$members);
}
private static function applyOneTransaction(
PhabricatorProject $project,
PhabricatorUser $user,
$type,
$new_value) {
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType($type);
$xaction->setNewValue($new_value);
$editor = new PhabricatorProjectEditor($project);
$editor->setUser($user);
$editor->applyTransactions(array($xaction));
}
public function __construct(PhabricatorProject $project) {
$this->project = $project;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function applyTransactions(array $transactions) {
assert_instances_of($transactions, 'PhabricatorProjectTransaction');
if (!$this->user) {
throw new Exception('Call setUser() before save()!');
}
$user = $this->user;
$project = $this->project;
$is_new = !$project->getID();
if ($is_new) {
$project->setAuthorPHID($user->getPHID());
}
foreach ($transactions as $key => $xaction) {
- $type = $xaction->getTransactionType();
-
$this->setTransactionOldValue($project, $xaction);
-
if (!$this->transactionHasEffect($xaction)) {
unset($transactions[$key]);
continue;
}
+ }
- $this->applyTransactionEffect($project, $xaction);
+ if (!$is_new) {
+ // You must be able to view a project in order to edit it in any capacity.
+ PhabricatorPolicyFilter::requireCapability(
+ $user,
+ $project,
+ PhabricatorPolicyCapability::CAN_VIEW);
+
+ $need_edit = false;
+ $need_join = false;
+ foreach ($transactions as $key => $xaction) {
+ if ($this->getTransactionRequiresEditCapability($xaction)) {
+ $need_edit = true;
+ }
+ if ($this->getTransactionRequiresJoinCapability($xaction)) {
+ $need_join = true;
+ }
+ }
+
+ if ($need_edit) {
+ PhabricatorPolicyFilter::requireCapability(
+ $user,
+ $project,
+ PhabricatorPolicyCapability::CAN_EDIT);
+ }
+
+ if ($need_join) {
+ PhabricatorPolicyFilter::requireCapability(
+ $user,
+ $project,
+ PhabricatorPolicyCapability::CAN_JOIN);
+ }
}
if (!$transactions) {
return $this;
}
+ foreach ($transactions as $xaction) {
+ $this->applyTransactionEffect($project, $xaction);
+ }
+
try {
$project->openTransaction();
$project->save();
$edge_type = PhabricatorEdgeConfig::TYPE_PROJ_MEMBER;
$editor = new PhabricatorEdgeEditor();
$editor->setUser($this->user);
foreach ($this->remEdges as $phid) {
$editor->removeEdge($project->getPHID(), $edge_type, $phid);
}
foreach ($this->addEdges as $phid) {
$editor->addEdge($project->getPHID(), $edge_type, $phid);
}
$editor->save();
foreach ($transactions as $xaction) {
$xaction->setAuthorPHID($user->getPHID());
$xaction->setProjectID($project->getID());
$xaction->save();
}
$project->saveTransaction();
foreach ($transactions as $xaction) {
$this->publishTransactionStory($project, $xaction);
}
} catch (AphrontQueryDuplicateKeyException $ex) {
// We already validated the slug, but might race. Try again to see if
// that's the issue. If it is, we'll throw a more specific exception. If
// not, throw the original exception.
$this->validateName($project);
throw $ex;
}
// TODO: If we rename a project, we should move its Phriction page. Do
// that once Phriction supports document moves.
return $this;
}
private function validateName(PhabricatorProject $project) {
$slug = $project->getPhrictionSlug();
$name = $project->getName();
if ($slug == '/') {
throw new PhabricatorProjectNameCollisionException(
"Project names must be unique and contain some letters or numbers.");
}
$id = $project->getID();
$collision = id(new PhabricatorProject())->loadOneWhere(
'(name = %s OR phrictionSlug = %s) AND id %Q %nd',
$name,
$slug,
$id ? '!=' : 'IS NOT',
$id ? $id : null);
if ($collision) {
$other_name = $collision->getName();
$other_id = $collision->getID();
throw new PhabricatorProjectNameCollisionException(
"Project names must be unique. The name '{$name}' is too similar to ".
"the name of another project, '{$other_name}' (Project ID: ".
"{$other_id}). Choose a unique name.");
}
}
private function setTransactionOldValue(
PhabricatorProject $project,
PhabricatorProjectTransaction $xaction) {
$type = $xaction->getTransactionType();
switch ($type) {
case PhabricatorProjectTransactionType::TYPE_NAME:
$xaction->setOldValue($project->getName());
break;
case PhabricatorProjectTransactionType::TYPE_STATUS:
$xaction->setOldValue($project->getStatus());
break;
case PhabricatorProjectTransactionType::TYPE_MEMBERS:
$member_phids = $project->loadMemberPHIDs();
$project->attachMemberPHIDs($member_phids);
$old_value = array_values($member_phids);
$xaction->setOldValue($old_value);
$new_value = $xaction->getNewValue();
$new_value = array_filter($new_value);
$new_value = array_unique($new_value);
$new_value = array_values($new_value);
$xaction->setNewValue($new_value);
break;
+ case PhabricatorProjectTransactionType::TYPE_CAN_VIEW:
+ $xaction->setOldValue($project->getViewPolicy());
+ break;
+ case PhabricatorProjectTransactionType::TYPE_CAN_EDIT:
+ $xaction->setOldValue($project->getEditPolicy());
+ break;
+ case PhabricatorProjectTransactionType::TYPE_CAN_JOIN:
+ $xaction->setOldValue($project->getJoinPolicy());
+ break;
default:
throw new Exception("Unknown transaction type '{$type}'!");
}
return $this;
}
private function applyTransactionEffect(
PhabricatorProject $project,
PhabricatorProjectTransaction $xaction) {
$type = $xaction->getTransactionType();
switch ($type) {
case PhabricatorProjectTransactionType::TYPE_NAME:
$project->setName($xaction->getNewValue());
$project->setPhrictionSlug($xaction->getNewValue());
$this->validateName($project);
break;
case PhabricatorProjectTransactionType::TYPE_STATUS:
$project->setStatus($xaction->getNewValue());
break;
case PhabricatorProjectTransactionType::TYPE_MEMBERS:
$old = array_fill_keys($xaction->getOldValue(), true);
$new = array_fill_keys($xaction->getNewValue(), true);
$this->addEdges = array_keys(array_diff_key($new, $old));
$this->remEdges = array_keys(array_diff_key($old, $new));
break;
+ case PhabricatorProjectTransactionType::TYPE_CAN_VIEW:
+ $project->setViewPolicy($xaction->getNewValue());
+ break;
+ case PhabricatorProjectTransactionType::TYPE_CAN_EDIT:
+ $project->setEditPolicy($xaction->getNewValue());
+
+ // You can't edit away your ability to edit the project.
+ PhabricatorPolicyFilter::mustRetainCapability(
+ $this->user,
+ $project,
+ PhabricatorPolicyCapability::CAN_EDIT);
+ break;
+ case PhabricatorProjectTransactionType::TYPE_CAN_JOIN:
+ $project->setJoinPolicy($xaction->getNewValue());
+ break;
default:
throw new Exception("Unknown transaction type '{$type}'!");
}
}
private function publishTransactionStory(
PhabricatorProject $project,
PhabricatorProjectTransaction $xaction) {
$related_phids = array(
$project->getPHID(),
$xaction->getAuthorPHID(),
);
id(new PhabricatorFeedStoryPublisher())
->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_PROJECT)
->setStoryData(
array(
'projectPHID' => $project->getPHID(),
'transactionID' => $xaction->getID(),
'type' => $xaction->getTransactionType(),
'old' => $xaction->getOldValue(),
'new' => $xaction->getNewValue(),
))
->setStoryTime(time())
->setStoryAuthorPHID($xaction->getAuthorPHID())
->setRelatedPHIDs($related_phids)
->publish();
}
private function transactionHasEffect(
PhabricatorProjectTransaction $xaction) {
return ($xaction->getOldValue() !== $xaction->getNewValue());
}
+
+ /**
+ * All transactions except joining or leaving a project require edit
+ * capability.
+ */
+ private function getTransactionRequiresEditCapability(
+ PhabricatorProjectTransaction $xaction) {
+ return ($this->isJoinOrLeaveTransaction($xaction) === null);
+ }
+
+
+ /**
+ * Joining a project requires the join capability. Anyone leave a project.
+ */
+ private function getTransactionRequiresJoinCapability(
+ PhabricatorProjectTransaction $xaction) {
+ $type = $this->isJoinOrLeaveTransaction($xaction);
+ return ($type == 'join');
+ }
+
+
+ /**
+ * Returns 'join' if this transaction causes the acting user ONLY to join the
+ * project.
+ *
+ * Returns 'leave' if this transaction causes the acting user ONLY to leave
+ * the project.
+ *
+ * Returns null in all other cases.
+ */
+ private function isJoinOrLeaveTransaction(
+ PhabricatorProjectTransaction $xaction) {
+
+ $type = $xaction->getTransactionType();
+ if ($type != PhabricatorProjectTransactionType::TYPE_MEMBERS) {
+ return null;
+ }
+
+ switch ($type) {
+ case PhabricatorProjectTransactionType::TYPE_MEMBERS:
+ $old = $xaction->getOldValue();
+ $new = $xaction->getNewValue();
+
+ $add = array_diff($new, $old);
+ $rem = array_diff($old, $new);
+
+ if (count($add) > 1) {
+ return null;
+ } else if (count($add) == 1) {
+ if (reset($add) != $this->user->getPHID()) {
+ return null;
+ } else {
+ return 'join';
+ }
+ }
+
+ if (count($rem) > 1) {
+ return null;
+ } else if (count($rem) == 1) {
+ if (reset($rem) != $this->user->getPHID()) {
+ return null;
+ } else {
+ return 'leave';
+ }
+ }
+ break;
+ }
+
+ return true;
+ }
+
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Jan 19 2025, 22:40 (6 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1129495
Default Alt Text
(43 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment