mirror of
https://we.phorge.it/source/phorge.git
synced 2025-02-09 21:38:29 +01:00
Summary: Ref T13242. See PHI1039. Maniphest subtypes generally seem to be working well. I designed them as a general capability that might be extended to other `EditEngine` objects later, and PHI1039 describes a situation where extending subtypes to projects would give us some reasonable tools. (Some installs also already use icons/colors as a sort of lightweight version of subtypes, so I believe this is generally useful capability.) Some of this is a little bit copy-pasted and could probably be shared, but I'd like to wait a bit longer before merging it. For example, both configs have exactly the same structure right now, but Projects should possibly have some different flags (for example: to disable creating subprojects / milestones). This implementation is pretty basic for now: notably, subprojects/milestones don't get the nice "choose from among subtype forms" treatment that tasks do. If this ends up being part of a solution to PHI1039, I'd plan to fill that in later on. Test Plan: Defined multiple subtypes, created subtype forms, created projects with appropriate subtypes. Filtered them by subtype. Saw subtype information on list/detail views. Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13242 Differential Revision: https://secure.phabricator.com/D20040
907 lines
24 KiB
PHP
907 lines
24 KiB
PHP
<?php
|
|
|
|
final class PhabricatorProject extends PhabricatorProjectDAO
|
|
implements
|
|
PhabricatorApplicationTransactionInterface,
|
|
PhabricatorFlaggableInterface,
|
|
PhabricatorPolicyInterface,
|
|
PhabricatorExtendedPolicyInterface,
|
|
PhabricatorCustomFieldInterface,
|
|
PhabricatorDestructibleInterface,
|
|
PhabricatorFulltextInterface,
|
|
PhabricatorFerretInterface,
|
|
PhabricatorConduitResultInterface,
|
|
PhabricatorColumnProxyInterface,
|
|
PhabricatorSpacesInterface,
|
|
PhabricatorEditEngineSubtypeInterface {
|
|
|
|
protected $name;
|
|
protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
|
|
protected $authorPHID;
|
|
protected $primarySlug;
|
|
protected $profileImagePHID;
|
|
protected $icon;
|
|
protected $color;
|
|
protected $mailKey;
|
|
|
|
protected $viewPolicy;
|
|
protected $editPolicy;
|
|
protected $joinPolicy;
|
|
protected $isMembershipLocked;
|
|
|
|
protected $parentProjectPHID;
|
|
protected $hasWorkboard;
|
|
protected $hasMilestones;
|
|
protected $hasSubprojects;
|
|
protected $milestoneNumber;
|
|
|
|
protected $projectPath;
|
|
protected $projectDepth;
|
|
protected $projectPathKey;
|
|
|
|
protected $properties = array();
|
|
protected $spacePHID;
|
|
protected $subtype;
|
|
|
|
private $memberPHIDs = self::ATTACHABLE;
|
|
private $watcherPHIDs = self::ATTACHABLE;
|
|
private $sparseWatchers = self::ATTACHABLE;
|
|
private $sparseMembers = self::ATTACHABLE;
|
|
private $customFields = self::ATTACHABLE;
|
|
private $profileImageFile = self::ATTACHABLE;
|
|
private $slugs = self::ATTACHABLE;
|
|
private $parentProject = self::ATTACHABLE;
|
|
|
|
const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken';
|
|
|
|
const ITEM_PICTURE = 'project.picture';
|
|
const ITEM_PROFILE = 'project.profile';
|
|
const ITEM_POINTS = 'project.points';
|
|
const ITEM_WORKBOARD = 'project.workboard';
|
|
const ITEM_MEMBERS = 'project.members';
|
|
const ITEM_MANAGE = 'project.manage';
|
|
const ITEM_MILESTONES = 'project.milestones';
|
|
const ITEM_SUBPROJECTS = 'project.subprojects';
|
|
|
|
public static function initializeNewProject(
|
|
PhabricatorUser $actor,
|
|
PhabricatorProject $parent = null) {
|
|
|
|
$app = id(new PhabricatorApplicationQuery())
|
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
|
->withClasses(array('PhabricatorProjectApplication'))
|
|
->executeOne();
|
|
|
|
$view_policy = $app->getPolicy(
|
|
ProjectDefaultViewCapability::CAPABILITY);
|
|
$edit_policy = $app->getPolicy(
|
|
ProjectDefaultEditCapability::CAPABILITY);
|
|
$join_policy = $app->getPolicy(
|
|
ProjectDefaultJoinCapability::CAPABILITY);
|
|
|
|
// If this is the child of some other project, default the Space to the
|
|
// Space of the parent.
|
|
if ($parent) {
|
|
$space_phid = $parent->getSpacePHID();
|
|
} else {
|
|
$space_phid = $actor->getDefaultSpacePHID();
|
|
}
|
|
|
|
$default_icon = PhabricatorProjectIconSet::getDefaultIconKey();
|
|
$default_color = PhabricatorProjectIconSet::getDefaultColorKey();
|
|
|
|
return id(new PhabricatorProject())
|
|
->setAuthorPHID($actor->getPHID())
|
|
->setIcon($default_icon)
|
|
->setColor($default_color)
|
|
->setViewPolicy($view_policy)
|
|
->setEditPolicy($edit_policy)
|
|
->setJoinPolicy($join_policy)
|
|
->setSpacePHID($space_phid)
|
|
->setIsMembershipLocked(0)
|
|
->attachMemberPHIDs(array())
|
|
->attachSlugs(array())
|
|
->setHasWorkboard(0)
|
|
->setHasMilestones(0)
|
|
->setHasSubprojects(0)
|
|
->setSubtype(PhabricatorEditEngineSubtype::SUBTYPE_DEFAULT)
|
|
->attachParentProject(null);
|
|
}
|
|
|
|
public function getCapabilities() {
|
|
return array(
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
PhabricatorPolicyCapability::CAN_EDIT,
|
|
PhabricatorPolicyCapability::CAN_JOIN,
|
|
);
|
|
}
|
|
|
|
public function getPolicy($capability) {
|
|
if ($this->isMilestone()) {
|
|
return $this->getParentProject()->getPolicy($capability);
|
|
}
|
|
|
|
switch ($capability) {
|
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
|
return $this->getViewPolicy();
|
|
case PhabricatorPolicyCapability::CAN_EDIT:
|
|
return $this->getEditPolicy();
|
|
case PhabricatorPolicyCapability::CAN_JOIN:
|
|
return $this->getJoinPolicy();
|
|
}
|
|
}
|
|
|
|
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
|
if ($this->isMilestone()) {
|
|
return $this->getParentProject()->hasAutomaticCapability(
|
|
$capability,
|
|
$viewer);
|
|
}
|
|
|
|
$can_edit = PhabricatorPolicyCapability::CAN_EDIT;
|
|
|
|
switch ($capability) {
|
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
|
if ($this->isUserMember($viewer->getPHID())) {
|
|
// Project members can always view a project.
|
|
return true;
|
|
}
|
|
break;
|
|
case PhabricatorPolicyCapability::CAN_EDIT:
|
|
$parent = $this->getParentProject();
|
|
if ($parent) {
|
|
$can_edit_parent = PhabricatorPolicyFilter::hasCapability(
|
|
$viewer,
|
|
$parent,
|
|
$can_edit);
|
|
if ($can_edit_parent) {
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
case PhabricatorPolicyCapability::CAN_JOIN:
|
|
if (PhabricatorPolicyFilter::hasCapability($viewer, $this, $can_edit)) {
|
|
// Project editors can always join a project.
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function describeAutomaticCapability($capability) {
|
|
|
|
// TODO: Clarify the additional rules that parent and subprojects imply.
|
|
|
|
switch ($capability) {
|
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
|
return pht('Members of a project can always view it.');
|
|
case PhabricatorPolicyCapability::CAN_JOIN:
|
|
return pht('Users who can edit a project can always join it.');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
|
|
$extended = array();
|
|
|
|
switch ($capability) {
|
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
|
$parent = $this->getParentProject();
|
|
if ($parent) {
|
|
$extended[] = array(
|
|
$parent,
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return $extended;
|
|
}
|
|
|
|
public function isUserMember($user_phid) {
|
|
if ($this->memberPHIDs !== self::ATTACHABLE) {
|
|
return in_array($user_phid, $this->memberPHIDs);
|
|
}
|
|
return $this->assertAttachedKey($this->sparseMembers, $user_phid);
|
|
}
|
|
|
|
public function setIsUserMember($user_phid, $is_member) {
|
|
if ($this->sparseMembers === self::ATTACHABLE) {
|
|
$this->sparseMembers = array();
|
|
}
|
|
$this->sparseMembers[$user_phid] = $is_member;
|
|
return $this;
|
|
}
|
|
|
|
protected function getConfiguration() {
|
|
return array(
|
|
self::CONFIG_AUX_PHID => true,
|
|
self::CONFIG_SERIALIZATION => array(
|
|
'properties' => self::SERIALIZATION_JSON,
|
|
),
|
|
self::CONFIG_COLUMN_SCHEMA => array(
|
|
'name' => 'sort128',
|
|
'status' => 'text32',
|
|
'primarySlug' => 'text128?',
|
|
'isMembershipLocked' => 'bool',
|
|
'profileImagePHID' => 'phid?',
|
|
'icon' => 'text32',
|
|
'color' => 'text32',
|
|
'mailKey' => 'bytes20',
|
|
'joinPolicy' => 'policy',
|
|
'parentProjectPHID' => 'phid?',
|
|
'hasWorkboard' => 'bool',
|
|
'hasMilestones' => 'bool',
|
|
'hasSubprojects' => 'bool',
|
|
'milestoneNumber' => 'uint32?',
|
|
'projectPath' => 'hashpath64',
|
|
'projectDepth' => 'uint32',
|
|
'projectPathKey' => 'bytes4',
|
|
'subtype' => 'text64',
|
|
),
|
|
self::CONFIG_KEY_SCHEMA => array(
|
|
'key_icon' => array(
|
|
'columns' => array('icon'),
|
|
),
|
|
'key_color' => array(
|
|
'columns' => array('color'),
|
|
),
|
|
'key_milestone' => array(
|
|
'columns' => array('parentProjectPHID', 'milestoneNumber'),
|
|
'unique' => true,
|
|
),
|
|
'key_primaryslug' => array(
|
|
'columns' => array('primarySlug'),
|
|
'unique' => true,
|
|
),
|
|
'key_path' => array(
|
|
'columns' => array('projectPath', 'projectDepth'),
|
|
),
|
|
'key_pathkey' => array(
|
|
'columns' => array('projectPathKey'),
|
|
'unique' => true,
|
|
),
|
|
),
|
|
) + parent::getConfiguration();
|
|
}
|
|
|
|
public function generatePHID() {
|
|
return PhabricatorPHID::generateNewPHID(
|
|
PhabricatorProjectProjectPHIDType::TYPECONST);
|
|
}
|
|
|
|
public function attachMemberPHIDs(array $phids) {
|
|
$this->memberPHIDs = $phids;
|
|
return $this;
|
|
}
|
|
|
|
public function getMemberPHIDs() {
|
|
return $this->assertAttached($this->memberPHIDs);
|
|
}
|
|
|
|
public function isArchived() {
|
|
return ($this->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED);
|
|
}
|
|
|
|
public function getProfileImageURI() {
|
|
return $this->getProfileImageFile()->getBestURI();
|
|
}
|
|
|
|
public function attachProfileImageFile(PhabricatorFile $file) {
|
|
$this->profileImageFile = $file;
|
|
return $this;
|
|
}
|
|
|
|
public function getProfileImageFile() {
|
|
return $this->assertAttached($this->profileImageFile);
|
|
}
|
|
|
|
|
|
public function isUserWatcher($user_phid) {
|
|
if ($this->watcherPHIDs !== self::ATTACHABLE) {
|
|
return in_array($user_phid, $this->watcherPHIDs);
|
|
}
|
|
return $this->assertAttachedKey($this->sparseWatchers, $user_phid);
|
|
}
|
|
|
|
public function isUserAncestorWatcher($user_phid) {
|
|
$is_watcher = $this->isUserWatcher($user_phid);
|
|
|
|
if (!$is_watcher) {
|
|
$parent = $this->getParentProject();
|
|
if ($parent) {
|
|
return $parent->isUserWatcher($user_phid);
|
|
}
|
|
}
|
|
|
|
return $is_watcher;
|
|
}
|
|
|
|
public function getWatchedAncestorPHID($user_phid) {
|
|
if ($this->isUserWatcher($user_phid)) {
|
|
return $this->getPHID();
|
|
}
|
|
|
|
$parent = $this->getParentProject();
|
|
if ($parent) {
|
|
return $parent->getWatchedAncestorPHID($user_phid);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function setIsUserWatcher($user_phid, $is_watcher) {
|
|
if ($this->sparseWatchers === self::ATTACHABLE) {
|
|
$this->sparseWatchers = array();
|
|
}
|
|
$this->sparseWatchers[$user_phid] = $is_watcher;
|
|
return $this;
|
|
}
|
|
|
|
public function attachWatcherPHIDs(array $phids) {
|
|
$this->watcherPHIDs = $phids;
|
|
return $this;
|
|
}
|
|
|
|
public function getWatcherPHIDs() {
|
|
return $this->assertAttached($this->watcherPHIDs);
|
|
}
|
|
|
|
public function getAllAncestorWatcherPHIDs() {
|
|
$parent = $this->getParentProject();
|
|
if ($parent) {
|
|
$watchers = $parent->getAllAncestorWatcherPHIDs();
|
|
} else {
|
|
$watchers = array();
|
|
}
|
|
|
|
foreach ($this->getWatcherPHIDs() as $phid) {
|
|
$watchers[$phid] = $phid;
|
|
}
|
|
|
|
return $watchers;
|
|
}
|
|
|
|
public function attachSlugs(array $slugs) {
|
|
$this->slugs = $slugs;
|
|
return $this;
|
|
}
|
|
|
|
public function getSlugs() {
|
|
return $this->assertAttached($this->slugs);
|
|
}
|
|
|
|
public function getColor() {
|
|
if ($this->isArchived()) {
|
|
return PHUITagView::COLOR_DISABLED;
|
|
}
|
|
|
|
return $this->color;
|
|
}
|
|
|
|
public function getURI() {
|
|
$id = $this->getID();
|
|
return "/project/view/{$id}/";
|
|
}
|
|
|
|
public function getProfileURI() {
|
|
$id = $this->getID();
|
|
return "/project/profile/{$id}/";
|
|
}
|
|
|
|
public function save() {
|
|
if (!$this->getMailKey()) {
|
|
$this->setMailKey(Filesystem::readRandomCharacters(20));
|
|
}
|
|
|
|
if (!strlen($this->getPHID())) {
|
|
$this->setPHID($this->generatePHID());
|
|
}
|
|
|
|
if (!strlen($this->getProjectPathKey())) {
|
|
$hash = PhabricatorHash::digestForIndex($this->getPHID());
|
|
$hash = substr($hash, 0, 4);
|
|
$this->setProjectPathKey($hash);
|
|
}
|
|
|
|
$path = array();
|
|
$depth = 0;
|
|
if ($this->parentProjectPHID) {
|
|
$parent = $this->getParentProject();
|
|
$path[] = $parent->getProjectPath();
|
|
$depth = $parent->getProjectDepth() + 1;
|
|
}
|
|
$path[] = $this->getProjectPathKey();
|
|
$path = implode('', $path);
|
|
|
|
$limit = self::getProjectDepthLimit();
|
|
if ($depth >= $limit) {
|
|
throw new Exception(pht('Project depth is too great.'));
|
|
}
|
|
|
|
$this->setProjectPath($path);
|
|
$this->setProjectDepth($depth);
|
|
|
|
$this->openTransaction();
|
|
$result = parent::save();
|
|
$this->updateDatasourceTokens();
|
|
$this->saveTransaction();
|
|
|
|
return $result;
|
|
}
|
|
|
|
public static function getProjectDepthLimit() {
|
|
// This is limited by how many path hashes we can fit in the path
|
|
// column.
|
|
return 16;
|
|
}
|
|
|
|
public function updateDatasourceTokens() {
|
|
$table = self::TABLE_DATASOURCE_TOKEN;
|
|
$conn_w = $this->establishConnection('w');
|
|
$id = $this->getID();
|
|
|
|
$slugs = queryfx_all(
|
|
$conn_w,
|
|
'SELECT * FROM %T WHERE projectPHID = %s',
|
|
id(new PhabricatorProjectSlug())->getTableName(),
|
|
$this->getPHID());
|
|
|
|
$all_strings = ipull($slugs, 'slug');
|
|
$all_strings[] = $this->getDisplayName();
|
|
$all_strings = implode(' ', $all_strings);
|
|
|
|
$tokens = PhabricatorTypeaheadDatasource::tokenizeString($all_strings);
|
|
|
|
$sql = array();
|
|
foreach ($tokens as $token) {
|
|
$sql[] = qsprintf($conn_w, '(%d, %s)', $id, $token);
|
|
}
|
|
|
|
$this->openTransaction();
|
|
queryfx(
|
|
$conn_w,
|
|
'DELETE FROM %T WHERE projectID = %d',
|
|
$table,
|
|
$id);
|
|
|
|
foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
|
|
queryfx(
|
|
$conn_w,
|
|
'INSERT INTO %T (projectID, token) VALUES %LQ',
|
|
$table,
|
|
$chunk);
|
|
}
|
|
$this->saveTransaction();
|
|
}
|
|
|
|
public function isMilestone() {
|
|
return ($this->getMilestoneNumber() !== null);
|
|
}
|
|
|
|
public function getParentProject() {
|
|
return $this->assertAttached($this->parentProject);
|
|
}
|
|
|
|
public function attachParentProject(PhabricatorProject $project = null) {
|
|
$this->parentProject = $project;
|
|
return $this;
|
|
}
|
|
|
|
public function getAncestorProjectPaths() {
|
|
$parts = array();
|
|
|
|
$path = $this->getProjectPath();
|
|
$parent_length = (strlen($path) - 4);
|
|
|
|
for ($ii = $parent_length; $ii > 0; $ii -= 4) {
|
|
$parts[] = substr($path, 0, $ii);
|
|
}
|
|
|
|
return $parts;
|
|
}
|
|
|
|
public function getAncestorProjects() {
|
|
$ancestors = array();
|
|
|
|
$cursor = $this->getParentProject();
|
|
while ($cursor) {
|
|
$ancestors[] = $cursor;
|
|
$cursor = $cursor->getParentProject();
|
|
}
|
|
|
|
return $ancestors;
|
|
}
|
|
|
|
public function supportsEditMembers() {
|
|
if ($this->isMilestone()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->getHasSubprojects()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function supportsMilestones() {
|
|
if ($this->isMilestone()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function supportsSubprojects() {
|
|
if ($this->isMilestone()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function loadNextMilestoneNumber() {
|
|
$current = queryfx_one(
|
|
$this->establishConnection('w'),
|
|
'SELECT MAX(milestoneNumber) n
|
|
FROM %T
|
|
WHERE parentProjectPHID = %s',
|
|
$this->getTableName(),
|
|
$this->getPHID());
|
|
|
|
if (!$current) {
|
|
$number = 1;
|
|
} else {
|
|
$number = (int)$current['n'] + 1;
|
|
}
|
|
|
|
return $number;
|
|
}
|
|
|
|
public function getDisplayName() {
|
|
$name = $this->getName();
|
|
|
|
// If this is a milestone, show it as "Parent > Sprint 99".
|
|
if ($this->isMilestone()) {
|
|
$name = pht(
|
|
'%s (%s)',
|
|
$this->getParentProject()->getName(),
|
|
$name);
|
|
}
|
|
|
|
return $name;
|
|
}
|
|
|
|
public function getDisplayIconKey() {
|
|
if ($this->isMilestone()) {
|
|
$key = PhabricatorProjectIconSet::getMilestoneIconKey();
|
|
} else {
|
|
$key = $this->getIcon();
|
|
}
|
|
|
|
return $key;
|
|
}
|
|
|
|
public function getDisplayIconIcon() {
|
|
$key = $this->getDisplayIconKey();
|
|
return PhabricatorProjectIconSet::getIconIcon($key);
|
|
}
|
|
|
|
public function getDisplayIconName() {
|
|
$key = $this->getDisplayIconKey();
|
|
return PhabricatorProjectIconSet::getIconName($key);
|
|
}
|
|
|
|
public function getDisplayColor() {
|
|
if ($this->isMilestone()) {
|
|
return $this->getParentProject()->getColor();
|
|
}
|
|
|
|
return $this->getColor();
|
|
}
|
|
|
|
public function getDisplayIconComposeIcon() {
|
|
$icon = $this->getDisplayIconIcon();
|
|
return $icon;
|
|
}
|
|
|
|
public function getDisplayIconComposeColor() {
|
|
$color = $this->getDisplayColor();
|
|
|
|
$map = array(
|
|
'grey' => 'charcoal',
|
|
'checkered' => 'backdrop',
|
|
);
|
|
|
|
return idx($map, $color, $color);
|
|
}
|
|
|
|
public function getProperty($key, $default = null) {
|
|
return idx($this->properties, $key, $default);
|
|
}
|
|
|
|
public function setProperty($key, $value) {
|
|
$this->properties[$key] = $value;
|
|
return $this;
|
|
}
|
|
|
|
public function getDefaultWorkboardSort() {
|
|
return $this->getProperty('workboard.sort.default');
|
|
}
|
|
|
|
public function setDefaultWorkboardSort($sort) {
|
|
return $this->setProperty('workboard.sort.default', $sort);
|
|
}
|
|
|
|
public function getDefaultWorkboardFilter() {
|
|
return $this->getProperty('workboard.filter.default');
|
|
}
|
|
|
|
public function setDefaultWorkboardFilter($filter) {
|
|
return $this->setProperty('workboard.filter.default', $filter);
|
|
}
|
|
|
|
public function getWorkboardBackgroundColor() {
|
|
return $this->getProperty('workboard.background');
|
|
}
|
|
|
|
public function setWorkboardBackgroundColor($color) {
|
|
return $this->setProperty('workboard.background', $color);
|
|
}
|
|
|
|
public function getDisplayWorkboardBackgroundColor() {
|
|
$color = $this->getWorkboardBackgroundColor();
|
|
|
|
if ($color === null) {
|
|
$parent = $this->getParentProject();
|
|
if ($parent) {
|
|
return $parent->getDisplayWorkboardBackgroundColor();
|
|
}
|
|
}
|
|
|
|
if ($color === 'none') {
|
|
$color = null;
|
|
}
|
|
|
|
return $color;
|
|
}
|
|
|
|
|
|
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
|
|
|
|
|
|
public function getCustomFieldSpecificationForRole($role) {
|
|
return PhabricatorEnv::getEnvConfig('projects.fields');
|
|
}
|
|
|
|
public function getCustomFieldBaseClass() {
|
|
return 'PhabricatorProjectCustomField';
|
|
}
|
|
|
|
public function getCustomFields() {
|
|
return $this->assertAttached($this->customFields);
|
|
}
|
|
|
|
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
|
|
$this->customFields = $fields;
|
|
return $this;
|
|
}
|
|
|
|
|
|
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
|
|
|
|
|
public function getApplicationTransactionEditor() {
|
|
return new PhabricatorProjectTransactionEditor();
|
|
}
|
|
|
|
public function getApplicationTransactionTemplate() {
|
|
return new PhabricatorProjectTransaction();
|
|
}
|
|
|
|
|
|
/* -( PhabricatorSpacesInterface )----------------------------------------- */
|
|
|
|
|
|
public function getSpacePHID() {
|
|
if ($this->isMilestone()) {
|
|
return $this->getParentProject()->getSpacePHID();
|
|
}
|
|
return $this->spacePHID;
|
|
}
|
|
|
|
|
|
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
|
|
|
|
|
public function destroyObjectPermanently(
|
|
PhabricatorDestructionEngine $engine) {
|
|
|
|
$this->openTransaction();
|
|
$this->delete();
|
|
|
|
$columns = id(new PhabricatorProjectColumn())
|
|
->loadAllWhere('projectPHID = %s', $this->getPHID());
|
|
foreach ($columns as $column) {
|
|
$engine->destroyObject($column);
|
|
}
|
|
|
|
$slugs = id(new PhabricatorProjectSlug())
|
|
->loadAllWhere('projectPHID = %s', $this->getPHID());
|
|
foreach ($slugs as $slug) {
|
|
$slug->delete();
|
|
}
|
|
|
|
$this->saveTransaction();
|
|
}
|
|
|
|
|
|
/* -( PhabricatorFulltextInterface )--------------------------------------- */
|
|
|
|
|
|
public function newFulltextEngine() {
|
|
return new PhabricatorProjectFulltextEngine();
|
|
}
|
|
|
|
|
|
/* -( PhabricatorFerretInterface )--------------------------------------- */
|
|
|
|
|
|
public function newFerretEngine() {
|
|
return new PhabricatorProjectFerretEngine();
|
|
}
|
|
|
|
|
|
/* -( PhabricatorConduitResultInterface )---------------------------------- */
|
|
|
|
|
|
public function getFieldSpecificationsForConduit() {
|
|
return array(
|
|
id(new PhabricatorConduitSearchFieldSpecification())
|
|
->setKey('name')
|
|
->setType('string')
|
|
->setDescription(pht('The name of the project.')),
|
|
id(new PhabricatorConduitSearchFieldSpecification())
|
|
->setKey('slug')
|
|
->setType('string')
|
|
->setDescription(pht('Primary slug/hashtag.')),
|
|
id(new PhabricatorConduitSearchFieldSpecification())
|
|
->setKey('subtype')
|
|
->setType('string')
|
|
->setDescription(pht('Subtype of the project.')),
|
|
id(new PhabricatorConduitSearchFieldSpecification())
|
|
->setKey('milestone')
|
|
->setType('int?')
|
|
->setDescription(pht('For milestones, milestone sequence number.')),
|
|
id(new PhabricatorConduitSearchFieldSpecification())
|
|
->setKey('parent')
|
|
->setType('map<string, wild>?')
|
|
->setDescription(
|
|
pht(
|
|
'For subprojects and milestones, a brief description of the '.
|
|
'parent project.')),
|
|
id(new PhabricatorConduitSearchFieldSpecification())
|
|
->setKey('depth')
|
|
->setType('int')
|
|
->setDescription(
|
|
pht(
|
|
'For subprojects and milestones, depth of this project in the '.
|
|
'tree. Root projects have depth 0.')),
|
|
id(new PhabricatorConduitSearchFieldSpecification())
|
|
->setKey('icon')
|
|
->setType('map<string, wild>')
|
|
->setDescription(pht('Information about the project icon.')),
|
|
id(new PhabricatorConduitSearchFieldSpecification())
|
|
->setKey('color')
|
|
->setType('map<string, wild>')
|
|
->setDescription(pht('Information about the project color.')),
|
|
);
|
|
}
|
|
|
|
public function getFieldValuesForConduit() {
|
|
$color_key = $this->getColor();
|
|
$color_name = PhabricatorProjectIconSet::getColorName($color_key);
|
|
|
|
if ($this->isMilestone()) {
|
|
$milestone = (int)$this->getMilestoneNumber();
|
|
} else {
|
|
$milestone = null;
|
|
}
|
|
|
|
$parent = $this->getParentProject();
|
|
if ($parent) {
|
|
$parent_ref = $parent->getRefForConduit();
|
|
} else {
|
|
$parent_ref = null;
|
|
}
|
|
|
|
return array(
|
|
'name' => $this->getName(),
|
|
'slug' => $this->getPrimarySlug(),
|
|
'subtype' => $this->getSubtype(),
|
|
'milestone' => $milestone,
|
|
'depth' => (int)$this->getProjectDepth(),
|
|
'parent' => $parent_ref,
|
|
'icon' => array(
|
|
'key' => $this->getDisplayIconKey(),
|
|
'name' => $this->getDisplayIconName(),
|
|
'icon' => $this->getDisplayIconIcon(),
|
|
),
|
|
'color' => array(
|
|
'key' => $color_key,
|
|
'name' => $color_name,
|
|
),
|
|
);
|
|
}
|
|
|
|
public function getConduitSearchAttachments() {
|
|
return array(
|
|
id(new PhabricatorProjectsMembersSearchEngineAttachment())
|
|
->setAttachmentKey('members'),
|
|
id(new PhabricatorProjectsWatchersSearchEngineAttachment())
|
|
->setAttachmentKey('watchers'),
|
|
id(new PhabricatorProjectsAncestorsSearchEngineAttachment())
|
|
->setAttachmentKey('ancestors'),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get an abbreviated representation of this project for use in providing
|
|
* "parent" and "ancestor" information.
|
|
*/
|
|
public function getRefForConduit() {
|
|
return array(
|
|
'id' => (int)$this->getID(),
|
|
'phid' => $this->getPHID(),
|
|
'name' => $this->getName(),
|
|
);
|
|
}
|
|
|
|
|
|
/* -( PhabricatorColumnProxyInterface )------------------------------------ */
|
|
|
|
|
|
public function getProxyColumnName() {
|
|
return $this->getName();
|
|
}
|
|
|
|
public function getProxyColumnIcon() {
|
|
return $this->getDisplayIconIcon();
|
|
}
|
|
|
|
public function getProxyColumnClass() {
|
|
if ($this->isMilestone()) {
|
|
return 'phui-workboard-column-milestone';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
/* -( PhabricatorEditEngineSubtypeInterface )------------------------------ */
|
|
|
|
|
|
public function getEditEngineSubtype() {
|
|
return $this->getSubtype();
|
|
}
|
|
|
|
public function setEditEngineSubtype($value) {
|
|
return $this->setSubtype($value);
|
|
}
|
|
|
|
public function newEditEngineSubtypeMap() {
|
|
$config = PhabricatorEnv::getEnvConfig('projects.subtypes');
|
|
return PhabricatorEditEngineSubtype::newSubtypeMap($config);
|
|
}
|
|
|
|
public function newSubtypeObject() {
|
|
$subtype_key = $this->getEditEngineSubtype();
|
|
$subtype_map = $this->newEditEngineSubtypeMap();
|
|
return $subtype_map->getSubtype($subtype_key);
|
|
}
|
|
|
|
}
|