PageRenderTime 59ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/src/applications/project/storage/PhabricatorProject.php

http://github.com/facebook/phabricator
PHP | 918 lines | 719 code | 178 blank | 21 comment | 51 complexity | 56aadf5083ebeb8cff6f65d0a603bc65 MD5 | raw file
Possible License(s): JSON, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause, LGPL-2.0, MIT, LGPL-2.1, LGPL-3.0
  1. <?php
  2. final class PhabricatorProject extends PhabricatorProjectDAO
  3. implements
  4. PhabricatorApplicationTransactionInterface,
  5. PhabricatorFlaggableInterface,
  6. PhabricatorPolicyInterface,
  7. PhabricatorExtendedPolicyInterface,
  8. PhabricatorCustomFieldInterface,
  9. PhabricatorDestructibleInterface,
  10. PhabricatorFulltextInterface,
  11. PhabricatorFerretInterface,
  12. PhabricatorConduitResultInterface,
  13. PhabricatorColumnProxyInterface,
  14. PhabricatorSpacesInterface,
  15. PhabricatorEditEngineSubtypeInterface,
  16. PhabricatorWorkboardInterface {
  17. protected $name;
  18. protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
  19. protected $authorPHID;
  20. protected $primarySlug;
  21. protected $profileImagePHID;
  22. protected $icon;
  23. protected $color;
  24. protected $mailKey;
  25. protected $viewPolicy;
  26. protected $editPolicy;
  27. protected $joinPolicy;
  28. protected $isMembershipLocked;
  29. protected $parentProjectPHID;
  30. protected $hasWorkboard;
  31. protected $hasMilestones;
  32. protected $hasSubprojects;
  33. protected $milestoneNumber;
  34. protected $projectPath;
  35. protected $projectDepth;
  36. protected $projectPathKey;
  37. protected $properties = array();
  38. protected $spacePHID;
  39. protected $subtype;
  40. private $memberPHIDs = self::ATTACHABLE;
  41. private $watcherPHIDs = self::ATTACHABLE;
  42. private $sparseWatchers = self::ATTACHABLE;
  43. private $sparseMembers = self::ATTACHABLE;
  44. private $customFields = self::ATTACHABLE;
  45. private $profileImageFile = self::ATTACHABLE;
  46. private $slugs = self::ATTACHABLE;
  47. private $parentProject = self::ATTACHABLE;
  48. const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken';
  49. const ITEM_PICTURE = 'project.picture';
  50. const ITEM_PROFILE = 'project.profile';
  51. const ITEM_POINTS = 'project.points';
  52. const ITEM_WORKBOARD = 'project.workboard';
  53. const ITEM_REPORTS = 'project.reports';
  54. const ITEM_MEMBERS = 'project.members';
  55. const ITEM_MANAGE = 'project.manage';
  56. const ITEM_MILESTONES = 'project.milestones';
  57. const ITEM_SUBPROJECTS = 'project.subprojects';
  58. public static function initializeNewProject(
  59. PhabricatorUser $actor,
  60. PhabricatorProject $parent = null) {
  61. $app = id(new PhabricatorApplicationQuery())
  62. ->setViewer(PhabricatorUser::getOmnipotentUser())
  63. ->withClasses(array('PhabricatorProjectApplication'))
  64. ->executeOne();
  65. $view_policy = $app->getPolicy(
  66. ProjectDefaultViewCapability::CAPABILITY);
  67. $edit_policy = $app->getPolicy(
  68. ProjectDefaultEditCapability::CAPABILITY);
  69. $join_policy = $app->getPolicy(
  70. ProjectDefaultJoinCapability::CAPABILITY);
  71. // If this is the child of some other project, default the Space to the
  72. // Space of the parent.
  73. if ($parent) {
  74. $space_phid = $parent->getSpacePHID();
  75. } else {
  76. $space_phid = $actor->getDefaultSpacePHID();
  77. }
  78. $default_icon = PhabricatorProjectIconSet::getDefaultIconKey();
  79. $default_color = PhabricatorProjectIconSet::getDefaultColorKey();
  80. return id(new PhabricatorProject())
  81. ->setAuthorPHID($actor->getPHID())
  82. ->setIcon($default_icon)
  83. ->setColor($default_color)
  84. ->setViewPolicy($view_policy)
  85. ->setEditPolicy($edit_policy)
  86. ->setJoinPolicy($join_policy)
  87. ->setSpacePHID($space_phid)
  88. ->setIsMembershipLocked(0)
  89. ->attachMemberPHIDs(array())
  90. ->attachSlugs(array())
  91. ->setHasWorkboard(0)
  92. ->setHasMilestones(0)
  93. ->setHasSubprojects(0)
  94. ->setSubtype(PhabricatorEditEngineSubtype::SUBTYPE_DEFAULT)
  95. ->attachParentProject($parent);
  96. }
  97. public function getCapabilities() {
  98. return array(
  99. PhabricatorPolicyCapability::CAN_VIEW,
  100. PhabricatorPolicyCapability::CAN_EDIT,
  101. PhabricatorPolicyCapability::CAN_JOIN,
  102. );
  103. }
  104. public function getPolicy($capability) {
  105. if ($this->isMilestone()) {
  106. return $this->getParentProject()->getPolicy($capability);
  107. }
  108. switch ($capability) {
  109. case PhabricatorPolicyCapability::CAN_VIEW:
  110. return $this->getViewPolicy();
  111. case PhabricatorPolicyCapability::CAN_EDIT:
  112. return $this->getEditPolicy();
  113. case PhabricatorPolicyCapability::CAN_JOIN:
  114. return $this->getJoinPolicy();
  115. }
  116. }
  117. public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
  118. if ($this->isMilestone()) {
  119. return $this->getParentProject()->hasAutomaticCapability(
  120. $capability,
  121. $viewer);
  122. }
  123. $can_edit = PhabricatorPolicyCapability::CAN_EDIT;
  124. switch ($capability) {
  125. case PhabricatorPolicyCapability::CAN_VIEW:
  126. if ($this->isUserMember($viewer->getPHID())) {
  127. // Project members can always view a project.
  128. return true;
  129. }
  130. break;
  131. case PhabricatorPolicyCapability::CAN_EDIT:
  132. $parent = $this->getParentProject();
  133. if ($parent) {
  134. $can_edit_parent = PhabricatorPolicyFilter::hasCapability(
  135. $viewer,
  136. $parent,
  137. $can_edit);
  138. if ($can_edit_parent) {
  139. return true;
  140. }
  141. }
  142. break;
  143. case PhabricatorPolicyCapability::CAN_JOIN:
  144. if (PhabricatorPolicyFilter::hasCapability($viewer, $this, $can_edit)) {
  145. // Project editors can always join a project.
  146. return true;
  147. }
  148. break;
  149. }
  150. return false;
  151. }
  152. public function describeAutomaticCapability($capability) {
  153. // TODO: Clarify the additional rules that parent and subprojects imply.
  154. switch ($capability) {
  155. case PhabricatorPolicyCapability::CAN_VIEW:
  156. return pht('Members of a project can always view it.');
  157. case PhabricatorPolicyCapability::CAN_JOIN:
  158. return pht('Users who can edit a project can always join it.');
  159. }
  160. return null;
  161. }
  162. public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
  163. $extended = array();
  164. switch ($capability) {
  165. case PhabricatorPolicyCapability::CAN_VIEW:
  166. $parent = $this->getParentProject();
  167. if ($parent) {
  168. $extended[] = array(
  169. $parent,
  170. PhabricatorPolicyCapability::CAN_VIEW,
  171. );
  172. }
  173. break;
  174. }
  175. return $extended;
  176. }
  177. public function isUserMember($user_phid) {
  178. if ($this->memberPHIDs !== self::ATTACHABLE) {
  179. return in_array($user_phid, $this->memberPHIDs);
  180. }
  181. return $this->assertAttachedKey($this->sparseMembers, $user_phid);
  182. }
  183. public function setIsUserMember($user_phid, $is_member) {
  184. if ($this->sparseMembers === self::ATTACHABLE) {
  185. $this->sparseMembers = array();
  186. }
  187. $this->sparseMembers[$user_phid] = $is_member;
  188. return $this;
  189. }
  190. protected function getConfiguration() {
  191. return array(
  192. self::CONFIG_AUX_PHID => true,
  193. self::CONFIG_SERIALIZATION => array(
  194. 'properties' => self::SERIALIZATION_JSON,
  195. ),
  196. self::CONFIG_COLUMN_SCHEMA => array(
  197. 'name' => 'sort128',
  198. 'status' => 'text32',
  199. 'primarySlug' => 'text128?',
  200. 'isMembershipLocked' => 'bool',
  201. 'profileImagePHID' => 'phid?',
  202. 'icon' => 'text32',
  203. 'color' => 'text32',
  204. 'mailKey' => 'bytes20',
  205. 'joinPolicy' => 'policy',
  206. 'parentProjectPHID' => 'phid?',
  207. 'hasWorkboard' => 'bool',
  208. 'hasMilestones' => 'bool',
  209. 'hasSubprojects' => 'bool',
  210. 'milestoneNumber' => 'uint32?',
  211. 'projectPath' => 'hashpath64',
  212. 'projectDepth' => 'uint32',
  213. 'projectPathKey' => 'bytes4',
  214. 'subtype' => 'text64',
  215. ),
  216. self::CONFIG_KEY_SCHEMA => array(
  217. 'key_icon' => array(
  218. 'columns' => array('icon'),
  219. ),
  220. 'key_color' => array(
  221. 'columns' => array('color'),
  222. ),
  223. 'key_milestone' => array(
  224. 'columns' => array('parentProjectPHID', 'milestoneNumber'),
  225. 'unique' => true,
  226. ),
  227. 'key_primaryslug' => array(
  228. 'columns' => array('primarySlug'),
  229. 'unique' => true,
  230. ),
  231. 'key_path' => array(
  232. 'columns' => array('projectPath', 'projectDepth'),
  233. ),
  234. 'key_pathkey' => array(
  235. 'columns' => array('projectPathKey'),
  236. 'unique' => true,
  237. ),
  238. ),
  239. ) + parent::getConfiguration();
  240. }
  241. public function generatePHID() {
  242. return PhabricatorPHID::generateNewPHID(
  243. PhabricatorProjectProjectPHIDType::TYPECONST);
  244. }
  245. public function attachMemberPHIDs(array $phids) {
  246. $this->memberPHIDs = $phids;
  247. return $this;
  248. }
  249. public function getMemberPHIDs() {
  250. return $this->assertAttached($this->memberPHIDs);
  251. }
  252. public function isArchived() {
  253. return ($this->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED);
  254. }
  255. public function getProfileImageURI() {
  256. return $this->getProfileImageFile()->getBestURI();
  257. }
  258. public function attachProfileImageFile(PhabricatorFile $file) {
  259. $this->profileImageFile = $file;
  260. return $this;
  261. }
  262. public function getProfileImageFile() {
  263. return $this->assertAttached($this->profileImageFile);
  264. }
  265. public function isUserWatcher($user_phid) {
  266. if ($this->watcherPHIDs !== self::ATTACHABLE) {
  267. return in_array($user_phid, $this->watcherPHIDs);
  268. }
  269. return $this->assertAttachedKey($this->sparseWatchers, $user_phid);
  270. }
  271. public function isUserAncestorWatcher($user_phid) {
  272. $is_watcher = $this->isUserWatcher($user_phid);
  273. if (!$is_watcher) {
  274. $parent = $this->getParentProject();
  275. if ($parent) {
  276. return $parent->isUserWatcher($user_phid);
  277. }
  278. }
  279. return $is_watcher;
  280. }
  281. public function getWatchedAncestorPHID($user_phid) {
  282. if ($this->isUserWatcher($user_phid)) {
  283. return $this->getPHID();
  284. }
  285. $parent = $this->getParentProject();
  286. if ($parent) {
  287. return $parent->getWatchedAncestorPHID($user_phid);
  288. }
  289. return null;
  290. }
  291. public function setIsUserWatcher($user_phid, $is_watcher) {
  292. if ($this->sparseWatchers === self::ATTACHABLE) {
  293. $this->sparseWatchers = array();
  294. }
  295. $this->sparseWatchers[$user_phid] = $is_watcher;
  296. return $this;
  297. }
  298. public function attachWatcherPHIDs(array $phids) {
  299. $this->watcherPHIDs = $phids;
  300. return $this;
  301. }
  302. public function getWatcherPHIDs() {
  303. return $this->assertAttached($this->watcherPHIDs);
  304. }
  305. public function getAllAncestorWatcherPHIDs() {
  306. $parent = $this->getParentProject();
  307. if ($parent) {
  308. $watchers = $parent->getAllAncestorWatcherPHIDs();
  309. } else {
  310. $watchers = array();
  311. }
  312. foreach ($this->getWatcherPHIDs() as $phid) {
  313. $watchers[$phid] = $phid;
  314. }
  315. return $watchers;
  316. }
  317. public function attachSlugs(array $slugs) {
  318. $this->slugs = $slugs;
  319. return $this;
  320. }
  321. public function getSlugs() {
  322. return $this->assertAttached($this->slugs);
  323. }
  324. public function getColor() {
  325. if ($this->isArchived()) {
  326. return PHUITagView::COLOR_DISABLED;
  327. }
  328. return $this->color;
  329. }
  330. public function getURI() {
  331. $id = $this->getID();
  332. return "/project/view/{$id}/";
  333. }
  334. public function getProfileURI() {
  335. $id = $this->getID();
  336. return "/project/profile/{$id}/";
  337. }
  338. public function getWorkboardURI() {
  339. return urisprintf('/project/board/%d/', $this->getID());
  340. }
  341. public function getReportsURI() {
  342. return urisprintf('/project/reports/%d/', $this->getID());
  343. }
  344. public function save() {
  345. if (!$this->getMailKey()) {
  346. $this->setMailKey(Filesystem::readRandomCharacters(20));
  347. }
  348. if (!strlen($this->getPHID())) {
  349. $this->setPHID($this->generatePHID());
  350. }
  351. if (!strlen($this->getProjectPathKey())) {
  352. $hash = PhabricatorHash::digestForIndex($this->getPHID());
  353. $hash = substr($hash, 0, 4);
  354. $this->setProjectPathKey($hash);
  355. }
  356. $path = array();
  357. $depth = 0;
  358. if ($this->parentProjectPHID) {
  359. $parent = $this->getParentProject();
  360. $path[] = $parent->getProjectPath();
  361. $depth = $parent->getProjectDepth() + 1;
  362. }
  363. $path[] = $this->getProjectPathKey();
  364. $path = implode('', $path);
  365. $limit = self::getProjectDepthLimit();
  366. if ($depth >= $limit) {
  367. throw new Exception(pht('Project depth is too great.'));
  368. }
  369. $this->setProjectPath($path);
  370. $this->setProjectDepth($depth);
  371. $this->openTransaction();
  372. $result = parent::save();
  373. $this->updateDatasourceTokens();
  374. $this->saveTransaction();
  375. return $result;
  376. }
  377. public static function getProjectDepthLimit() {
  378. // This is limited by how many path hashes we can fit in the path
  379. // column.
  380. return 16;
  381. }
  382. public function updateDatasourceTokens() {
  383. $table = self::TABLE_DATASOURCE_TOKEN;
  384. $conn_w = $this->establishConnection('w');
  385. $id = $this->getID();
  386. $slugs = queryfx_all(
  387. $conn_w,
  388. 'SELECT * FROM %T WHERE projectPHID = %s',
  389. id(new PhabricatorProjectSlug())->getTableName(),
  390. $this->getPHID());
  391. $all_strings = ipull($slugs, 'slug');
  392. $all_strings[] = $this->getDisplayName();
  393. $all_strings = implode(' ', $all_strings);
  394. $tokens = PhabricatorTypeaheadDatasource::tokenizeString($all_strings);
  395. $sql = array();
  396. foreach ($tokens as $token) {
  397. $sql[] = qsprintf($conn_w, '(%d, %s)', $id, $token);
  398. }
  399. $this->openTransaction();
  400. queryfx(
  401. $conn_w,
  402. 'DELETE FROM %T WHERE projectID = %d',
  403. $table,
  404. $id);
  405. foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
  406. queryfx(
  407. $conn_w,
  408. 'INSERT INTO %T (projectID, token) VALUES %LQ',
  409. $table,
  410. $chunk);
  411. }
  412. $this->saveTransaction();
  413. }
  414. public function isMilestone() {
  415. return ($this->getMilestoneNumber() !== null);
  416. }
  417. public function getParentProject() {
  418. return $this->assertAttached($this->parentProject);
  419. }
  420. public function attachParentProject(PhabricatorProject $project = null) {
  421. $this->parentProject = $project;
  422. return $this;
  423. }
  424. public function getAncestorProjectPaths() {
  425. $parts = array();
  426. $path = $this->getProjectPath();
  427. $parent_length = (strlen($path) - 4);
  428. for ($ii = $parent_length; $ii > 0; $ii -= 4) {
  429. $parts[] = substr($path, 0, $ii);
  430. }
  431. return $parts;
  432. }
  433. public function getAncestorProjects() {
  434. $ancestors = array();
  435. $cursor = $this->getParentProject();
  436. while ($cursor) {
  437. $ancestors[] = $cursor;
  438. $cursor = $cursor->getParentProject();
  439. }
  440. return $ancestors;
  441. }
  442. public function supportsEditMembers() {
  443. if ($this->isMilestone()) {
  444. return false;
  445. }
  446. if ($this->getHasSubprojects()) {
  447. return false;
  448. }
  449. return true;
  450. }
  451. public function supportsMilestones() {
  452. if ($this->isMilestone()) {
  453. return false;
  454. }
  455. return true;
  456. }
  457. public function supportsSubprojects() {
  458. if ($this->isMilestone()) {
  459. return false;
  460. }
  461. return true;
  462. }
  463. public function loadNextMilestoneNumber() {
  464. $current = queryfx_one(
  465. $this->establishConnection('w'),
  466. 'SELECT MAX(milestoneNumber) n
  467. FROM %T
  468. WHERE parentProjectPHID = %s',
  469. $this->getTableName(),
  470. $this->getPHID());
  471. if (!$current) {
  472. $number = 1;
  473. } else {
  474. $number = (int)$current['n'] + 1;
  475. }
  476. return $number;
  477. }
  478. public function getDisplayName() {
  479. $name = $this->getName();
  480. // If this is a milestone, show it as "Parent > Sprint 99".
  481. if ($this->isMilestone()) {
  482. $name = pht(
  483. '%s (%s)',
  484. $this->getParentProject()->getName(),
  485. $name);
  486. }
  487. return $name;
  488. }
  489. public function getDisplayIconKey() {
  490. if ($this->isMilestone()) {
  491. $key = PhabricatorProjectIconSet::getMilestoneIconKey();
  492. } else {
  493. $key = $this->getIcon();
  494. }
  495. return $key;
  496. }
  497. public function getDisplayIconIcon() {
  498. $key = $this->getDisplayIconKey();
  499. return PhabricatorProjectIconSet::getIconIcon($key);
  500. }
  501. public function getDisplayIconName() {
  502. $key = $this->getDisplayIconKey();
  503. return PhabricatorProjectIconSet::getIconName($key);
  504. }
  505. public function getDisplayColor() {
  506. if ($this->isMilestone()) {
  507. return $this->getParentProject()->getColor();
  508. }
  509. return $this->getColor();
  510. }
  511. public function getDisplayIconComposeIcon() {
  512. $icon = $this->getDisplayIconIcon();
  513. return $icon;
  514. }
  515. public function getDisplayIconComposeColor() {
  516. $color = $this->getDisplayColor();
  517. $map = array(
  518. 'grey' => 'charcoal',
  519. 'checkered' => 'backdrop',
  520. );
  521. return idx($map, $color, $color);
  522. }
  523. public function getProperty($key, $default = null) {
  524. return idx($this->properties, $key, $default);
  525. }
  526. public function setProperty($key, $value) {
  527. $this->properties[$key] = $value;
  528. return $this;
  529. }
  530. public function getDefaultWorkboardSort() {
  531. return $this->getProperty('workboard.sort.default');
  532. }
  533. public function setDefaultWorkboardSort($sort) {
  534. return $this->setProperty('workboard.sort.default', $sort);
  535. }
  536. public function getDefaultWorkboardFilter() {
  537. return $this->getProperty('workboard.filter.default');
  538. }
  539. public function setDefaultWorkboardFilter($filter) {
  540. return $this->setProperty('workboard.filter.default', $filter);
  541. }
  542. public function getWorkboardBackgroundColor() {
  543. return $this->getProperty('workboard.background');
  544. }
  545. public function setWorkboardBackgroundColor($color) {
  546. return $this->setProperty('workboard.background', $color);
  547. }
  548. public function getDisplayWorkboardBackgroundColor() {
  549. $color = $this->getWorkboardBackgroundColor();
  550. if ($color === null) {
  551. $parent = $this->getParentProject();
  552. if ($parent) {
  553. return $parent->getDisplayWorkboardBackgroundColor();
  554. }
  555. }
  556. if ($color === 'none') {
  557. $color = null;
  558. }
  559. return $color;
  560. }
  561. /* -( PhabricatorCustomFieldInterface )------------------------------------ */
  562. public function getCustomFieldSpecificationForRole($role) {
  563. return PhabricatorEnv::getEnvConfig('projects.fields');
  564. }
  565. public function getCustomFieldBaseClass() {
  566. return 'PhabricatorProjectCustomField';
  567. }
  568. public function getCustomFields() {
  569. return $this->assertAttached($this->customFields);
  570. }
  571. public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
  572. $this->customFields = $fields;
  573. return $this;
  574. }
  575. /* -( PhabricatorApplicationTransactionInterface )------------------------- */
  576. public function getApplicationTransactionEditor() {
  577. return new PhabricatorProjectTransactionEditor();
  578. }
  579. public function getApplicationTransactionTemplate() {
  580. return new PhabricatorProjectTransaction();
  581. }
  582. /* -( PhabricatorSpacesInterface )----------------------------------------- */
  583. public function getSpacePHID() {
  584. if ($this->isMilestone()) {
  585. return $this->getParentProject()->getSpacePHID();
  586. }
  587. return $this->spacePHID;
  588. }
  589. /* -( PhabricatorDestructibleInterface )----------------------------------- */
  590. public function destroyObjectPermanently(
  591. PhabricatorDestructionEngine $engine) {
  592. $this->openTransaction();
  593. $this->delete();
  594. $columns = id(new PhabricatorProjectColumn())
  595. ->loadAllWhere('projectPHID = %s', $this->getPHID());
  596. foreach ($columns as $column) {
  597. $engine->destroyObject($column);
  598. }
  599. $slugs = id(new PhabricatorProjectSlug())
  600. ->loadAllWhere('projectPHID = %s', $this->getPHID());
  601. foreach ($slugs as $slug) {
  602. $slug->delete();
  603. }
  604. $this->saveTransaction();
  605. }
  606. /* -( PhabricatorFulltextInterface )--------------------------------------- */
  607. public function newFulltextEngine() {
  608. return new PhabricatorProjectFulltextEngine();
  609. }
  610. /* -( PhabricatorFerretInterface )--------------------------------------- */
  611. public function newFerretEngine() {
  612. return new PhabricatorProjectFerretEngine();
  613. }
  614. /* -( PhabricatorConduitResultInterface )---------------------------------- */
  615. public function getFieldSpecificationsForConduit() {
  616. return array(
  617. id(new PhabricatorConduitSearchFieldSpecification())
  618. ->setKey('name')
  619. ->setType('string')
  620. ->setDescription(pht('The name of the project.')),
  621. id(new PhabricatorConduitSearchFieldSpecification())
  622. ->setKey('slug')
  623. ->setType('string')
  624. ->setDescription(pht('Primary slug/hashtag.')),
  625. id(new PhabricatorConduitSearchFieldSpecification())
  626. ->setKey('subtype')
  627. ->setType('string')
  628. ->setDescription(pht('Subtype of the project.')),
  629. id(new PhabricatorConduitSearchFieldSpecification())
  630. ->setKey('milestone')
  631. ->setType('int?')
  632. ->setDescription(pht('For milestones, milestone sequence number.')),
  633. id(new PhabricatorConduitSearchFieldSpecification())
  634. ->setKey('parent')
  635. ->setType('map<string, wild>?')
  636. ->setDescription(
  637. pht(
  638. 'For subprojects and milestones, a brief description of the '.
  639. 'parent project.')),
  640. id(new PhabricatorConduitSearchFieldSpecification())
  641. ->setKey('depth')
  642. ->setType('int')
  643. ->setDescription(
  644. pht(
  645. 'For subprojects and milestones, depth of this project in the '.
  646. 'tree. Root projects have depth 0.')),
  647. id(new PhabricatorConduitSearchFieldSpecification())
  648. ->setKey('icon')
  649. ->setType('map<string, wild>')
  650. ->setDescription(pht('Information about the project icon.')),
  651. id(new PhabricatorConduitSearchFieldSpecification())
  652. ->setKey('color')
  653. ->setType('map<string, wild>')
  654. ->setDescription(pht('Information about the project color.')),
  655. );
  656. }
  657. public function getFieldValuesForConduit() {
  658. $color_key = $this->getColor();
  659. $color_name = PhabricatorProjectIconSet::getColorName($color_key);
  660. if ($this->isMilestone()) {
  661. $milestone = (int)$this->getMilestoneNumber();
  662. } else {
  663. $milestone = null;
  664. }
  665. $parent = $this->getParentProject();
  666. if ($parent) {
  667. $parent_ref = $parent->getRefForConduit();
  668. } else {
  669. $parent_ref = null;
  670. }
  671. return array(
  672. 'name' => $this->getName(),
  673. 'slug' => $this->getPrimarySlug(),
  674. 'subtype' => $this->getSubtype(),
  675. 'milestone' => $milestone,
  676. 'depth' => (int)$this->getProjectDepth(),
  677. 'parent' => $parent_ref,
  678. 'icon' => array(
  679. 'key' => $this->getDisplayIconKey(),
  680. 'name' => $this->getDisplayIconName(),
  681. 'icon' => $this->getDisplayIconIcon(),
  682. ),
  683. 'color' => array(
  684. 'key' => $color_key,
  685. 'name' => $color_name,
  686. ),
  687. );
  688. }
  689. public function getConduitSearchAttachments() {
  690. return array(
  691. id(new PhabricatorProjectsMembersSearchEngineAttachment())
  692. ->setAttachmentKey('members'),
  693. id(new PhabricatorProjectsWatchersSearchEngineAttachment())
  694. ->setAttachmentKey('watchers'),
  695. id(new PhabricatorProjectsAncestorsSearchEngineAttachment())
  696. ->setAttachmentKey('ancestors'),
  697. );
  698. }
  699. /**
  700. * Get an abbreviated representation of this project for use in providing
  701. * "parent" and "ancestor" information.
  702. */
  703. public function getRefForConduit() {
  704. return array(
  705. 'id' => (int)$this->getID(),
  706. 'phid' => $this->getPHID(),
  707. 'name' => $this->getName(),
  708. );
  709. }
  710. /* -( PhabricatorColumnProxyInterface )------------------------------------ */
  711. public function getProxyColumnName() {
  712. return $this->getName();
  713. }
  714. public function getProxyColumnIcon() {
  715. return $this->getDisplayIconIcon();
  716. }
  717. public function getProxyColumnClass() {
  718. if ($this->isMilestone()) {
  719. return 'phui-workboard-column-milestone';
  720. }
  721. return null;
  722. }
  723. /* -( PhabricatorEditEngineSubtypeInterface )------------------------------ */
  724. public function getEditEngineSubtype() {
  725. return $this->getSubtype();
  726. }
  727. public function setEditEngineSubtype($value) {
  728. return $this->setSubtype($value);
  729. }
  730. public function newEditEngineSubtypeMap() {
  731. $config = PhabricatorEnv::getEnvConfig('projects.subtypes');
  732. return PhabricatorEditEngineSubtype::newSubtypeMap($config)
  733. ->setDatasource(new PhabricatorProjectSubtypeDatasource());
  734. }
  735. public function newSubtypeObject() {
  736. $subtype_key = $this->getEditEngineSubtype();
  737. $subtype_map = $this->newEditEngineSubtypeMap();
  738. return $subtype_map->getSubtype($subtype_key);
  739. }
  740. }