2015-12-27 15:45:53 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class PhabricatorProjectEditEngine
|
|
|
|
extends PhabricatorEditEngine {
|
|
|
|
|
|
|
|
const ENGINECONST = 'projects.project';
|
|
|
|
|
2015-12-27 14:16:36 +01:00
|
|
|
private $parentProject;
|
|
|
|
private $milestoneProject;
|
|
|
|
|
|
|
|
public function setParentProject(PhabricatorProject $parent_project) {
|
|
|
|
$this->parentProject = $parent_project;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getParentProject() {
|
|
|
|
return $this->parentProject;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setMilestoneProject(PhabricatorProject $milestone_project) {
|
|
|
|
$this->milestoneProject = $milestone_project;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getMilestoneProject() {
|
|
|
|
return $this->milestoneProject;
|
|
|
|
}
|
|
|
|
|
2015-12-27 15:45:53 +01:00
|
|
|
public function getEngineName() {
|
|
|
|
return pht('Projects');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getSummaryHeader() {
|
|
|
|
return pht('Configure Project Forms');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getSummaryText() {
|
|
|
|
return pht('Configure forms for creating projects.');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getEngineApplicationClass() {
|
|
|
|
return 'PhabricatorProjectApplication';
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function newEditableObject() {
|
2016-02-12 19:57:13 +01:00
|
|
|
return PhabricatorProject::initializeNewProject($this->getViewer());
|
2015-12-27 15:45:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function newObjectQuery() {
|
|
|
|
return id(new PhabricatorProjectQuery())
|
|
|
|
->needSlugs(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getObjectCreateTitleText($object) {
|
|
|
|
return pht('Create New Project');
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getObjectEditTitleText($object) {
|
2016-03-28 18:05:22 +02:00
|
|
|
return pht('Edit Project: %s', $object->getName());
|
2015-12-27 15:45:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function getObjectEditShortText($object) {
|
|
|
|
return $object->getName();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getObjectCreateShortText() {
|
|
|
|
return pht('Create Project');
|
|
|
|
}
|
|
|
|
|
2016-03-28 18:05:22 +02:00
|
|
|
protected function getObjectName() {
|
|
|
|
return pht('Project');
|
|
|
|
}
|
|
|
|
|
2015-12-27 15:45:53 +01:00
|
|
|
protected function getObjectViewURI($object) {
|
2016-01-20 02:02:29 +01:00
|
|
|
if ($this->getIsCreate()) {
|
|
|
|
return $object->getURI();
|
|
|
|
} else {
|
|
|
|
$id = $object->getID();
|
2016-01-24 01:02:29 +01:00
|
|
|
return "/project/manage/{$id}/";
|
2016-01-20 02:02:29 +01:00
|
|
|
}
|
2015-12-27 15:45:53 +01:00
|
|
|
}
|
|
|
|
|
2015-12-27 14:16:36 +01:00
|
|
|
protected function getObjectCreateCancelURI($object) {
|
|
|
|
$parent = $this->getParentProject();
|
|
|
|
$milestone = $this->getMilestoneProject();
|
2016-02-11 18:43:30 +01:00
|
|
|
|
|
|
|
if ($parent || $milestone) {
|
|
|
|
$id = nonempty($parent, $milestone)->getID();
|
|
|
|
return "/project/subprojects/{$id}/";
|
2015-12-27 14:16:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return parent::getObjectCreateCancelURI($object);
|
|
|
|
}
|
|
|
|
|
2015-12-27 15:45:53 +01:00
|
|
|
protected function getCreateNewObjectPolicy() {
|
|
|
|
return $this->getApplication()->getPolicy(
|
|
|
|
ProjectCreateProjectsCapability::CAPABILITY);
|
|
|
|
}
|
|
|
|
|
2015-12-29 13:38:21 +01:00
|
|
|
protected function willConfigureFields($object, array $fields) {
|
|
|
|
$is_milestone = ($this->getMilestoneProject() || $object->isMilestone());
|
|
|
|
|
|
|
|
$unavailable = array(
|
|
|
|
PhabricatorTransactions::TYPE_VIEW_POLICY,
|
|
|
|
PhabricatorTransactions::TYPE_EDIT_POLICY,
|
|
|
|
PhabricatorTransactions::TYPE_JOIN_POLICY,
|
Lock milestone projects to an automatic color/icon
Summary:
Ref T10010.
Currently, milestone subproject have editable icons/colors, but I don't think this is likely to be used much (the expectation is that milestones will be common and homogenous, and it doesn't make much sense to pick different icons for "Sprint 32" vs "Sprint 33", I think).
Locking the icon and color lets us simplify the form, make milestones more distinct, and potentially reuse the color later for other things (e.g., active/future/past or on time / overdue or whatever else) or just give them a special color to make them more visible.
The best argument for retaining this that I can come up with is that certain milestones may be special (e.g., Sprint 19 is a major release?), but you can just name it "Sprint 19 (v3.0!)" or something, which seems pretty good for now.
Also don't show milestones on task browse/list view.
Test Plan: {F1048532}
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T10010
Differential Revision: https://secure.phabricator.com/D14912
2015-12-30 12:35:51 +01:00
|
|
|
PhabricatorProjectTransaction::TYPE_ICON,
|
|
|
|
PhabricatorProjectTransaction::TYPE_COLOR,
|
2015-12-29 13:38:21 +01:00
|
|
|
);
|
|
|
|
$unavailable = array_fuse($unavailable);
|
|
|
|
|
|
|
|
if ($is_milestone) {
|
|
|
|
foreach ($fields as $key => $field) {
|
|
|
|
$xaction_type = $field->getTransactionType();
|
|
|
|
if (isset($unavailable[$xaction_type])) {
|
|
|
|
unset($fields[$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $fields;
|
|
|
|
}
|
|
|
|
|
2015-12-27 15:45:53 +01:00
|
|
|
protected function newBuiltinEngineConfigurations() {
|
|
|
|
$configuration = head(parent::newBuiltinEngineConfigurations());
|
|
|
|
|
|
|
|
// TODO: This whole method is clumsy, and the ordering for the custom
|
|
|
|
// field is especially clumsy. Maybe try to make this more natural to
|
|
|
|
// express.
|
|
|
|
|
|
|
|
$configuration
|
|
|
|
->setFieldOrder(
|
|
|
|
array(
|
2015-12-27 14:16:36 +01:00
|
|
|
'parent',
|
|
|
|
'milestone',
|
2016-02-12 19:57:13 +01:00
|
|
|
'milestone.previous',
|
2015-12-27 15:45:53 +01:00
|
|
|
'name',
|
|
|
|
'std:project:internal:description',
|
|
|
|
'icon',
|
|
|
|
'color',
|
|
|
|
'slugs',
|
|
|
|
));
|
|
|
|
|
|
|
|
return array(
|
|
|
|
$configuration,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function buildCustomEditFields($object) {
|
|
|
|
$slugs = mpull($object->getSlugs(), 'getSlug');
|
|
|
|
$slugs = array_fuse($slugs);
|
|
|
|
unset($slugs[$object->getPrimarySlug()]);
|
|
|
|
$slugs = array_values($slugs);
|
|
|
|
|
2015-12-27 14:16:36 +01:00
|
|
|
$milestone = $this->getMilestoneProject();
|
|
|
|
$parent = $this->getParentProject();
|
|
|
|
|
|
|
|
if ($parent) {
|
|
|
|
$parent_phid = $parent->getPHID();
|
|
|
|
} else {
|
|
|
|
$parent_phid = null;
|
|
|
|
}
|
|
|
|
|
2016-02-12 19:57:13 +01:00
|
|
|
$previous_milestone_phid = null;
|
2015-12-27 14:16:36 +01:00
|
|
|
if ($milestone) {
|
|
|
|
$milestone_phid = $milestone->getPHID();
|
2016-02-12 19:57:13 +01:00
|
|
|
|
|
|
|
// Load the current milestone so we can show the user a hint about what
|
|
|
|
// it was called, so they don't have to remember if the next one should
|
|
|
|
// be "Sprint 287" or "Sprint 278".
|
|
|
|
|
|
|
|
$number = ($milestone->loadNextMilestoneNumber() - 1);
|
|
|
|
if ($number > 0) {
|
|
|
|
$previous_milestone = id(new PhabricatorProjectQuery())
|
|
|
|
->setViewer($this->getViewer())
|
|
|
|
->withParentProjectPHIDs(array($milestone->getPHID()))
|
|
|
|
->withIsMilestone(true)
|
|
|
|
->withMilestoneNumberBetween($number, $number)
|
|
|
|
->executeOne();
|
|
|
|
if ($previous_milestone) {
|
|
|
|
$previous_milestone_phid = $previous_milestone->getPHID();
|
|
|
|
}
|
|
|
|
}
|
2015-12-27 14:16:36 +01:00
|
|
|
} else {
|
|
|
|
$milestone_phid = null;
|
|
|
|
}
|
|
|
|
|
2016-02-05 22:26:47 +01:00
|
|
|
$fields = array(
|
2015-12-27 14:16:36 +01:00
|
|
|
id(new PhabricatorHandlesEditField())
|
|
|
|
->setKey('parent')
|
|
|
|
->setLabel(pht('Parent'))
|
|
|
|
->setDescription(pht('Create a subproject of an existing project.'))
|
|
|
|
->setConduitDescription(
|
|
|
|
pht('Choose a parent project to create a subproject beneath.'))
|
|
|
|
->setConduitTypeDescription(pht('PHID of the parent project.'))
|
|
|
|
->setAliases(array('parentPHID'))
|
|
|
|
->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT)
|
|
|
|
->setHandleParameterType(new AphrontPHIDHTTPParameterType())
|
|
|
|
->setSingleValue($parent_phid)
|
|
|
|
->setIsReorderable(false)
|
|
|
|
->setIsDefaultable(false)
|
|
|
|
->setIsLockable(false)
|
|
|
|
->setIsLocked(true),
|
|
|
|
id(new PhabricatorHandlesEditField())
|
|
|
|
->setKey('milestone')
|
|
|
|
->setLabel(pht('Milestone Of'))
|
|
|
|
->setDescription(pht('Parent project to create a milestone for.'))
|
|
|
|
->setConduitDescription(
|
|
|
|
pht('Choose a parent project to create a new milestone for.'))
|
|
|
|
->setConduitTypeDescription(pht('PHID of the parent project.'))
|
|
|
|
->setAliases(array('milestonePHID'))
|
|
|
|
->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE)
|
|
|
|
->setHandleParameterType(new AphrontPHIDHTTPParameterType())
|
|
|
|
->setSingleValue($milestone_phid)
|
|
|
|
->setIsReorderable(false)
|
|
|
|
->setIsDefaultable(false)
|
|
|
|
->setIsLockable(false)
|
|
|
|
->setIsLocked(true),
|
2016-02-12 19:57:13 +01:00
|
|
|
id(new PhabricatorHandlesEditField())
|
|
|
|
->setKey('milestone.previous')
|
|
|
|
->setLabel(pht('Previous Milestone'))
|
|
|
|
->setSingleValue($previous_milestone_phid)
|
|
|
|
->setIsReorderable(false)
|
|
|
|
->setIsDefaultable(false)
|
|
|
|
->setIsLockable(false)
|
|
|
|
->setIsLocked(true),
|
2015-12-27 15:45:53 +01:00
|
|
|
id(new PhabricatorTextEditField())
|
|
|
|
->setKey('name')
|
|
|
|
->setLabel(pht('Name'))
|
|
|
|
->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME)
|
|
|
|
->setIsRequired(true)
|
|
|
|
->setDescription(pht('Project name.'))
|
|
|
|
->setConduitDescription(pht('Rename the project'))
|
|
|
|
->setConduitTypeDescription(pht('New project name.'))
|
|
|
|
->setValue($object->getName()),
|
|
|
|
id(new PhabricatorIconSetEditField())
|
|
|
|
->setKey('icon')
|
|
|
|
->setLabel(pht('Icon'))
|
|
|
|
->setTransactionType(PhabricatorProjectTransaction::TYPE_ICON)
|
|
|
|
->setIconSet(new PhabricatorProjectIconSet())
|
|
|
|
->setDescription(pht('Project icon.'))
|
|
|
|
->setConduitDescription(pht('Change the project icon.'))
|
|
|
|
->setConduitTypeDescription(pht('New project icon.'))
|
|
|
|
->setValue($object->getIcon()),
|
|
|
|
id(new PhabricatorSelectEditField())
|
|
|
|
->setKey('color')
|
|
|
|
->setLabel(pht('Color'))
|
|
|
|
->setTransactionType(PhabricatorProjectTransaction::TYPE_COLOR)
|
|
|
|
->setOptions(PhabricatorProjectIconSet::getColorMap())
|
|
|
|
->setDescription(pht('Project tag color.'))
|
|
|
|
->setConduitDescription(pht('Change the project tag color.'))
|
|
|
|
->setConduitTypeDescription(pht('New project tag color.'))
|
|
|
|
->setValue($object->getColor()),
|
|
|
|
id(new PhabricatorStringListEditField())
|
|
|
|
->setKey('slugs')
|
|
|
|
->setLabel(pht('Additional Hashtags'))
|
|
|
|
->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
|
|
|
|
->setDescription(pht('Additional project slugs.'))
|
|
|
|
->setConduitDescription(pht('Change project slugs.'))
|
|
|
|
->setConduitTypeDescription(pht('New list of slugs.'))
|
|
|
|
->setValue($slugs),
|
|
|
|
);
|
2016-02-05 22:26:47 +01:00
|
|
|
|
|
|
|
$can_edit_members = (!$milestone) &&
|
|
|
|
(!$object->isMilestone()) &&
|
|
|
|
(!$object->getHasSubprojects());
|
|
|
|
|
|
|
|
if ($can_edit_members) {
|
|
|
|
|
|
|
|
// Show this on the web UI when creating a project, but not when editing
|
|
|
|
// one. It is always available via Conduit.
|
|
|
|
$conduit_only = !$this->getIsCreate();
|
|
|
|
|
|
|
|
$members_field = id(new PhabricatorUsersEditField())
|
|
|
|
->setKey('members')
|
|
|
|
->setAliases(array('memberPHIDs'))
|
|
|
|
->setLabel(pht('Initial Members'))
|
|
|
|
->setIsConduitOnly($conduit_only)
|
|
|
|
->setUseEdgeTransactions(true)
|
|
|
|
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
|
|
|
->setMetadataValue(
|
|
|
|
'edge:type',
|
|
|
|
PhabricatorProjectProjectHasMemberEdgeType::EDGECONST)
|
|
|
|
->setDescription(pht('Initial project members.'))
|
|
|
|
->setConduitDescription(pht('Set project members.'))
|
|
|
|
->setConduitTypeDescription(pht('New list of members.'))
|
|
|
|
->setValue(array());
|
|
|
|
|
|
|
|
$members_field->setViewer($this->getViewer());
|
|
|
|
|
|
|
|
$edit_add = $members_field->getConduitEditType('members.add')
|
|
|
|
->setConduitDescription(pht('Add members.'));
|
|
|
|
|
|
|
|
$edit_set = $members_field->getConduitEditType('members.set')
|
|
|
|
->setConduitDescription(
|
|
|
|
pht('Set members, overwriting the current value.'));
|
|
|
|
|
|
|
|
$edit_rem = $members_field->getConduitEditType('members.remove')
|
|
|
|
->setConduitDescription(pht('Remove members.'));
|
|
|
|
|
|
|
|
$fields[] = $members_field;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $fields;
|
|
|
|
|
2015-12-27 15:45:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|