1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-02-10 13:58:34 +01:00
phorge-phorge/src/applications/project/storage/PhabricatorProject.php

908 lines
24 KiB
PHP
Raw Normal View History

<?php
final class PhabricatorProject extends PhabricatorProjectDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorFlaggableInterface,
Make Projects a PhabricatorSubscribableInterface, but with restricted defaults Summary: Ref T4379. I want project subscriptions to work like this (yell if this seems whacky, since it makes subscriptions mean somethign a little different for projects than they do for other objects): - You can only subscribe to a project if you're a project member. - When you're added as a member, you're added as a subscriber. - When you're removed as a member, you're removed as a subscriber. - While you're a member, you can optionally unsubscribe. From a UI perspective: - We don't show the subscriber list, since it's going to be some uninteresting subset of the member list. - We don't show CC transactions in history, since they're an uninteresting near-approximation of the membership transactions. - You only see the subscription controls if you're a member. To do this, I've augmented `PhabricatorSubscribableInterface` with two new methods. It would be nice if we were on PHP 5.4+ and could just use traits for this, but we should get data about version usage before we think about this. For now, copy/paste the default implementations into every implementing class. Then, I implemented the interface in `PhabricatorProject` but with alternate defaults. Test Plan: - Used the normal interaction on existing objects. - This has no actual effect on projects, verified no subscription stuff mysteriously appeared. - Hit the new error case by fiddling with the UI. Reviewers: btrahan Reviewed By: btrahan CC: chad, aran Maniphest Tasks: T4379 Differential Revision: https://secure.phabricator.com/D8165
2014-02-10 14:29:17 -08:00
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();
}
Allow installs to customize project icons Summary: Ref T10010. Ref T5819. General alignment of the stars: - There were some hacks in Conduit around stripping `fa-...` off icons when reading and writing that I wanted to get rid of. - We probably have room for a subtitle in the new heavy nav, and using the icon name is a good starting point (and maybe good enough on its own?) - The project list was real bad looking with redundant tag/names, now it is very slightly less bad looking with non-redundant types? - Some installs will want to call Milestones something else, and this gets us a big part of the way there. - This may slightly help to reinforce "tag" vs "policy" vs "group" stuff? --- I'm letting installs have enough rope to shoot themselves in the foot (e.g., define 100 icons). It isn't the end of the world if they reuse icons, and is clearly their fault. I think the cases where 100 icons will break down are: - Icon selector dialog may get very unwieldy. - Query UI will be pretty iffy/huge with 100 icons. We could improve these fairly easily if an install comes up with a reasonable use case for having 100 icons. --- The UI on the icon itself in the list views is a little iffy -- mostly, it's too saturated/bold. I'd ideally like to try either: - rendering a "shade" version (i.e. lighter, less-saturated color); or - rendering a "shade" tag with just the icon in it. However, there didn't seem to be a way to do the first one right now (`fa-example sh-blue` doesn't work) and the second one had weird margins/padding, so I left it like this for now. I figure we can clean it up once we build the thick nav, since that will probably also want an identical element. (I don't want to render a full tag with the icon + name since I think that's confusing -- it looks like a project/object tag, but is not.) Test Plan: {F1049905} {F1049906} Reviewers: chad Reviewed By: chad Subscribers: 20after4, Luke081515.2 Maniphest Tasks: T5819, T10010 Differential Revision: https://secure.phabricator.com/D14918
2015-12-30 04:36:48 -08:00
$default_icon = PhabricatorProjectIconSet::getDefaultIconKey();
$default_color = PhabricatorProjectIconSet::getDefaultColorKey();
Allow installs to customize project icons Summary: Ref T10010. Ref T5819. General alignment of the stars: - There were some hacks in Conduit around stripping `fa-...` off icons when reading and writing that I wanted to get rid of. - We probably have room for a subtitle in the new heavy nav, and using the icon name is a good starting point (and maybe good enough on its own?) - The project list was real bad looking with redundant tag/names, now it is very slightly less bad looking with non-redundant types? - Some installs will want to call Milestones something else, and this gets us a big part of the way there. - This may slightly help to reinforce "tag" vs "policy" vs "group" stuff? --- I'm letting installs have enough rope to shoot themselves in the foot (e.g., define 100 icons). It isn't the end of the world if they reuse icons, and is clearly their fault. I think the cases where 100 icons will break down are: - Icon selector dialog may get very unwieldy. - Query UI will be pretty iffy/huge with 100 icons. We could improve these fairly easily if an install comes up with a reasonable use case for having 100 icons. --- The UI on the icon itself in the list views is a little iffy -- mostly, it's too saturated/bold. I'd ideally like to try either: - rendering a "shade" version (i.e. lighter, less-saturated color); or - rendering a "shade" tag with just the icon in it. However, there didn't seem to be a way to do the first one right now (`fa-example sh-blue` doesn't work) and the second one had weird margins/padding, so I left it like this for now. I figure we can clean it up once we build the thick nav, since that will probably also want an identical element. (I don't want to render a full tag with the icon + name since I think that's confusing -- it looks like a project/object tag, but is not.) Test Plan: {F1049905} {F1049906} Reviewers: chad Reviewed By: chad Subscribers: 20after4, Luke081515.2 Maniphest Tasks: T5819, T10010 Differential Revision: https://secure.phabricator.com/D14918
2015-12-30 04:36:48 -08:00
return id(new PhabricatorProject())
->setAuthorPHID($actor->getPHID())
Allow installs to customize project icons Summary: Ref T10010. Ref T5819. General alignment of the stars: - There were some hacks in Conduit around stripping `fa-...` off icons when reading and writing that I wanted to get rid of. - We probably have room for a subtitle in the new heavy nav, and using the icon name is a good starting point (and maybe good enough on its own?) - The project list was real bad looking with redundant tag/names, now it is very slightly less bad looking with non-redundant types? - Some installs will want to call Milestones something else, and this gets us a big part of the way there. - This may slightly help to reinforce "tag" vs "policy" vs "group" stuff? --- I'm letting installs have enough rope to shoot themselves in the foot (e.g., define 100 icons). It isn't the end of the world if they reuse icons, and is clearly their fault. I think the cases where 100 icons will break down are: - Icon selector dialog may get very unwieldy. - Query UI will be pretty iffy/huge with 100 icons. We could improve these fairly easily if an install comes up with a reasonable use case for having 100 icons. --- The UI on the icon itself in the list views is a little iffy -- mostly, it's too saturated/bold. I'd ideally like to try either: - rendering a "shade" version (i.e. lighter, less-saturated color); or - rendering a "shade" tag with just the icon in it. However, there didn't seem to be a way to do the first one right now (`fa-example sh-blue` doesn't work) and the second one had weird margins/padding, so I left it like this for now. I figure we can clean it up once we build the thick nav, since that will probably also want an identical element. (I don't want to render a full tag with the icon + name since I think that's confusing -- it looks like a project/object tag, but is not.) Test Plan: {F1049905} {F1049906} Reviewers: chad Reviewed By: chad Subscribers: 20after4, Luke081515.2 Maniphest Tasks: T5819, T10010 Differential Revision: https://secure.phabricator.com/D14918
2015-12-30 04:36:48 -08:00
->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);
}
Make Projects a PhabricatorSubscribableInterface, but with restricted defaults Summary: Ref T4379. I want project subscriptions to work like this (yell if this seems whacky, since it makes subscriptions mean somethign a little different for projects than they do for other objects): - You can only subscribe to a project if you're a project member. - When you're added as a member, you're added as a subscriber. - When you're removed as a member, you're removed as a subscriber. - While you're a member, you can optionally unsubscribe. From a UI perspective: - We don't show the subscriber list, since it's going to be some uninteresting subset of the member list. - We don't show CC transactions in history, since they're an uninteresting near-approximation of the membership transactions. - You only see the subscription controls if you're a member. To do this, I've augmented `PhabricatorSubscribableInterface` with two new methods. It would be nice if we were on PHP 5.4+ and could just use traits for this, but we should get data about version usage before we think about this. For now, copy/paste the default implementations into every implementing class. Then, I implemented the interface in `PhabricatorProject` but with alternate defaults. Test Plan: - Used the normal interaction on existing objects. - This has no actual effect on projects, verified no subscription stuff mysteriously appeared. - Hit the new error case by fiddling with the UI. Reviewers: btrahan Reviewed By: btrahan CC: chad, aran Maniphest Tasks: T4379 Differential Revision: https://secure.phabricator.com/D8165
2014-02-10 14:29:17 -08:00
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;
}
Allow installs to customize project icons Summary: Ref T10010. Ref T5819. General alignment of the stars: - There were some hacks in Conduit around stripping `fa-...` off icons when reading and writing that I wanted to get rid of. - We probably have room for a subtitle in the new heavy nav, and using the icon name is a good starting point (and maybe good enough on its own?) - The project list was real bad looking with redundant tag/names, now it is very slightly less bad looking with non-redundant types? - Some installs will want to call Milestones something else, and this gets us a big part of the way there. - This may slightly help to reinforce "tag" vs "policy" vs "group" stuff? --- I'm letting installs have enough rope to shoot themselves in the foot (e.g., define 100 icons). It isn't the end of the world if they reuse icons, and is clearly their fault. I think the cases where 100 icons will break down are: - Icon selector dialog may get very unwieldy. - Query UI will be pretty iffy/huge with 100 icons. We could improve these fairly easily if an install comes up with a reasonable use case for having 100 icons. --- The UI on the icon itself in the list views is a little iffy -- mostly, it's too saturated/bold. I'd ideally like to try either: - rendering a "shade" version (i.e. lighter, less-saturated color); or - rendering a "shade" tag with just the icon in it. However, there didn't seem to be a way to do the first one right now (`fa-example sh-blue` doesn't work) and the second one had weird margins/padding, so I left it like this for now. I figure we can clean it up once we build the thick nav, since that will probably also want an identical element. (I don't want to render a full tag with the icon + name since I think that's confusing -- it looks like a project/object tag, but is not.) Test Plan: {F1049905} {F1049906} Reviewers: chad Reviewed By: chad Subscribers: 20after4, Luke081515.2 Maniphest Tasks: T5819, T10010 Differential Revision: https://secure.phabricator.com/D14918
2015-12-30 04:36:48 -08:00
public function getDisplayIconKey() {
if ($this->isMilestone()) {
Allow installs to customize project icons Summary: Ref T10010. Ref T5819. General alignment of the stars: - There were some hacks in Conduit around stripping `fa-...` off icons when reading and writing that I wanted to get rid of. - We probably have room for a subtitle in the new heavy nav, and using the icon name is a good starting point (and maybe good enough on its own?) - The project list was real bad looking with redundant tag/names, now it is very slightly less bad looking with non-redundant types? - Some installs will want to call Milestones something else, and this gets us a big part of the way there. - This may slightly help to reinforce "tag" vs "policy" vs "group" stuff? --- I'm letting installs have enough rope to shoot themselves in the foot (e.g., define 100 icons). It isn't the end of the world if they reuse icons, and is clearly their fault. I think the cases where 100 icons will break down are: - Icon selector dialog may get very unwieldy. - Query UI will be pretty iffy/huge with 100 icons. We could improve these fairly easily if an install comes up with a reasonable use case for having 100 icons. --- The UI on the icon itself in the list views is a little iffy -- mostly, it's too saturated/bold. I'd ideally like to try either: - rendering a "shade" version (i.e. lighter, less-saturated color); or - rendering a "shade" tag with just the icon in it. However, there didn't seem to be a way to do the first one right now (`fa-example sh-blue` doesn't work) and the second one had weird margins/padding, so I left it like this for now. I figure we can clean it up once we build the thick nav, since that will probably also want an identical element. (I don't want to render a full tag with the icon + name since I think that's confusing -- it looks like a project/object tag, but is not.) Test Plan: {F1049905} {F1049906} Reviewers: chad Reviewed By: chad Subscribers: 20after4, Luke081515.2 Maniphest Tasks: T5819, T10010 Differential Revision: https://secure.phabricator.com/D14918
2015-12-30 04:36:48 -08:00
$key = PhabricatorProjectIconSet::getMilestoneIconKey();
} else {
$key = $this->getIcon();
}
Allow installs to customize project icons Summary: Ref T10010. Ref T5819. General alignment of the stars: - There were some hacks in Conduit around stripping `fa-...` off icons when reading and writing that I wanted to get rid of. - We probably have room for a subtitle in the new heavy nav, and using the icon name is a good starting point (and maybe good enough on its own?) - The project list was real bad looking with redundant tag/names, now it is very slightly less bad looking with non-redundant types? - Some installs will want to call Milestones something else, and this gets us a big part of the way there. - This may slightly help to reinforce "tag" vs "policy" vs "group" stuff? --- I'm letting installs have enough rope to shoot themselves in the foot (e.g., define 100 icons). It isn't the end of the world if they reuse icons, and is clearly their fault. I think the cases where 100 icons will break down are: - Icon selector dialog may get very unwieldy. - Query UI will be pretty iffy/huge with 100 icons. We could improve these fairly easily if an install comes up with a reasonable use case for having 100 icons. --- The UI on the icon itself in the list views is a little iffy -- mostly, it's too saturated/bold. I'd ideally like to try either: - rendering a "shade" version (i.e. lighter, less-saturated color); or - rendering a "shade" tag with just the icon in it. However, there didn't seem to be a way to do the first one right now (`fa-example sh-blue` doesn't work) and the second one had weird margins/padding, so I left it like this for now. I figure we can clean it up once we build the thick nav, since that will probably also want an identical element. (I don't want to render a full tag with the icon + name since I think that's confusing -- it looks like a project/object tag, but is not.) Test Plan: {F1049905} {F1049906} Reviewers: chad Reviewed By: chad Subscribers: 20after4, Luke081515.2 Maniphest Tasks: T5819, T10010 Differential Revision: https://secure.phabricator.com/D14918
2015-12-30 04:36:48 -08:00
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.')),
Allow installs to customize project icons Summary: Ref T10010. Ref T5819. General alignment of the stars: - There were some hacks in Conduit around stripping `fa-...` off icons when reading and writing that I wanted to get rid of. - We probably have room for a subtitle in the new heavy nav, and using the icon name is a good starting point (and maybe good enough on its own?) - The project list was real bad looking with redundant tag/names, now it is very slightly less bad looking with non-redundant types? - Some installs will want to call Milestones something else, and this gets us a big part of the way there. - This may slightly help to reinforce "tag" vs "policy" vs "group" stuff? --- I'm letting installs have enough rope to shoot themselves in the foot (e.g., define 100 icons). It isn't the end of the world if they reuse icons, and is clearly their fault. I think the cases where 100 icons will break down are: - Icon selector dialog may get very unwieldy. - Query UI will be pretty iffy/huge with 100 icons. We could improve these fairly easily if an install comes up with a reasonable use case for having 100 icons. --- The UI on the icon itself in the list views is a little iffy -- mostly, it's too saturated/bold. I'd ideally like to try either: - rendering a "shade" version (i.e. lighter, less-saturated color); or - rendering a "shade" tag with just the icon in it. However, there didn't seem to be a way to do the first one right now (`fa-example sh-blue` doesn't work) and the second one had weird margins/padding, so I left it like this for now. I figure we can clean it up once we build the thick nav, since that will probably also want an identical element. (I don't want to render a full tag with the icon + name since I think that's confusing -- it looks like a project/object tag, but is not.) Test Plan: {F1049905} {F1049906} Reviewers: chad Reviewed By: chad Subscribers: 20after4, Luke081515.2 Maniphest Tasks: T5819, T10010 Differential Revision: https://secure.phabricator.com/D14918
2015-12-30 04:36:48 -08:00
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,
Allow installs to customize project icons Summary: Ref T10010. Ref T5819. General alignment of the stars: - There were some hacks in Conduit around stripping `fa-...` off icons when reading and writing that I wanted to get rid of. - We probably have room for a subtitle in the new heavy nav, and using the icon name is a good starting point (and maybe good enough on its own?) - The project list was real bad looking with redundant tag/names, now it is very slightly less bad looking with non-redundant types? - Some installs will want to call Milestones something else, and this gets us a big part of the way there. - This may slightly help to reinforce "tag" vs "policy" vs "group" stuff? --- I'm letting installs have enough rope to shoot themselves in the foot (e.g., define 100 icons). It isn't the end of the world if they reuse icons, and is clearly their fault. I think the cases where 100 icons will break down are: - Icon selector dialog may get very unwieldy. - Query UI will be pretty iffy/huge with 100 icons. We could improve these fairly easily if an install comes up with a reasonable use case for having 100 icons. --- The UI on the icon itself in the list views is a little iffy -- mostly, it's too saturated/bold. I'd ideally like to try either: - rendering a "shade" version (i.e. lighter, less-saturated color); or - rendering a "shade" tag with just the icon in it. However, there didn't seem to be a way to do the first one right now (`fa-example sh-blue` doesn't work) and the second one had weird margins/padding, so I left it like this for now. I figure we can clean it up once we build the thick nav, since that will probably also want an identical element. (I don't want to render a full tag with the icon + name since I think that's confusing -- it looks like a project/object tag, but is not.) Test Plan: {F1049905} {F1049906} Reviewers: chad Reviewed By: chad Subscribers: 20after4, Luke081515.2 Maniphest Tasks: T5819, T10010 Differential Revision: https://secure.phabricator.com/D14918
2015-12-30 04:36:48 -08:00
'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);
}
}