Changeset View
Changeset View
Standalone View
Standalone View
src/applications/project/storage/PhabricatorProject.php
| <?php | <?php | ||||
| final class PhabricatorProject extends PhabricatorProjectDAO | final class PhabricatorProject extends PhabricatorProjectDAO | ||||
| implements | implements | ||||
| PhabricatorApplicationTransactionInterface, | PhabricatorApplicationTransactionInterface, | ||||
| PhabricatorFlaggableInterface, | PhabricatorFlaggableInterface, | ||||
| PhabricatorPolicyInterface, | PhabricatorPolicyInterface, | ||||
| PhabricatorExtendedPolicyInterface, | |||||
| PhabricatorSubscribableInterface, | PhabricatorSubscribableInterface, | ||||
| PhabricatorCustomFieldInterface, | PhabricatorCustomFieldInterface, | ||||
| PhabricatorDestructibleInterface, | PhabricatorDestructibleInterface, | ||||
| PhabricatorFulltextInterface { | PhabricatorFulltextInterface { | ||||
| protected $name; | protected $name; | ||||
| protected $status = PhabricatorProjectStatus::STATUS_ACTIVE; | protected $status = PhabricatorProjectStatus::STATUS_ACTIVE; | ||||
| protected $authorPHID; | protected $authorPHID; | ||||
| Show All 9 Lines | final class PhabricatorProject extends PhabricatorProjectDAO | ||||
| protected $isMembershipLocked; | protected $isMembershipLocked; | ||||
| protected $parentProjectPHID; | protected $parentProjectPHID; | ||||
| protected $hasWorkboard; | protected $hasWorkboard; | ||||
| protected $hasMilestones; | protected $hasMilestones; | ||||
| protected $hasSubprojects; | protected $hasSubprojects; | ||||
| protected $milestoneNumber; | protected $milestoneNumber; | ||||
| protected $projectPath; | |||||
| protected $projectDepth; | |||||
| private $memberPHIDs = self::ATTACHABLE; | private $memberPHIDs = self::ATTACHABLE; | ||||
| private $watcherPHIDs = self::ATTACHABLE; | private $watcherPHIDs = self::ATTACHABLE; | ||||
| private $sparseWatchers = self::ATTACHABLE; | private $sparseWatchers = self::ATTACHABLE; | ||||
| private $sparseMembers = self::ATTACHABLE; | private $sparseMembers = self::ATTACHABLE; | ||||
| private $customFields = self::ATTACHABLE; | private $customFields = self::ATTACHABLE; | ||||
| private $profileImageFile = self::ATTACHABLE; | private $profileImageFile = self::ATTACHABLE; | ||||
| private $slugs = self::ATTACHABLE; | private $slugs = self::ATTACHABLE; | ||||
| private $parentProject = self::ATTACHABLE; | private $parentProject = self::ATTACHABLE; | ||||
| Show All 23 Lines | return id(new PhabricatorProject()) | ||||
| ->setViewPolicy($view_policy) | ->setViewPolicy($view_policy) | ||||
| ->setEditPolicy($edit_policy) | ->setEditPolicy($edit_policy) | ||||
| ->setJoinPolicy($join_policy) | ->setJoinPolicy($join_policy) | ||||
| ->setIsMembershipLocked(0) | ->setIsMembershipLocked(0) | ||||
| ->attachMemberPHIDs(array()) | ->attachMemberPHIDs(array()) | ||||
| ->attachSlugs(array()) | ->attachSlugs(array()) | ||||
| ->setHasWorkboard(0) | ->setHasWorkboard(0) | ||||
| ->setHasMilestones(0) | ->setHasMilestones(0) | ||||
| ->setHasSubprojects(0); | ->setHasSubprojects(0) | ||||
| ->attachParentProject(null); | |||||
| } | } | ||||
| public function getCapabilities() { | public function getCapabilities() { | ||||
| return array( | return array( | ||||
| PhabricatorPolicyCapability::CAN_VIEW, | PhabricatorPolicyCapability::CAN_VIEW, | ||||
| PhabricatorPolicyCapability::CAN_EDIT, | PhabricatorPolicyCapability::CAN_EDIT, | ||||
| PhabricatorPolicyCapability::CAN_JOIN, | PhabricatorPolicyCapability::CAN_JOIN, | ||||
| ); | ); | ||||
| } | } | ||||
| public function getPolicy($capability) { | public function getPolicy($capability) { | ||||
| switch ($capability) { | switch ($capability) { | ||||
| case PhabricatorPolicyCapability::CAN_VIEW: | case PhabricatorPolicyCapability::CAN_VIEW: | ||||
| return $this->getViewPolicy(); | return $this->getViewPolicy(); | ||||
| case PhabricatorPolicyCapability::CAN_EDIT: | case PhabricatorPolicyCapability::CAN_EDIT: | ||||
| return $this->getEditPolicy(); | return $this->getEditPolicy(); | ||||
| case PhabricatorPolicyCapability::CAN_JOIN: | case PhabricatorPolicyCapability::CAN_JOIN: | ||||
| return $this->getJoinPolicy(); | return $this->getJoinPolicy(); | ||||
| } | } | ||||
| } | } | ||||
| public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { | public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { | ||||
| $can_edit = PhabricatorPolicyCapability::CAN_EDIT; | |||||
| switch ($capability) { | switch ($capability) { | ||||
| case PhabricatorPolicyCapability::CAN_VIEW: | case PhabricatorPolicyCapability::CAN_VIEW: | ||||
| if ($this->isUserMember($viewer->getPHID())) { | if ($this->isUserMember($viewer->getPHID())) { | ||||
| // Project members can always view a project. | // Project members can always view a project. | ||||
| return true; | return true; | ||||
| } | } | ||||
| break; | break; | ||||
| case PhabricatorPolicyCapability::CAN_EDIT: | 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; | break; | ||||
| case PhabricatorPolicyCapability::CAN_JOIN: | case PhabricatorPolicyCapability::CAN_JOIN: | ||||
| $can_edit = PhabricatorPolicyCapability::CAN_EDIT; | |||||
| if (PhabricatorPolicyFilter::hasCapability($viewer, $this, $can_edit)) { | if (PhabricatorPolicyFilter::hasCapability($viewer, $this, $can_edit)) { | ||||
| // Project editors can always join a project. | // Project editors can always join a project. | ||||
| return true; | return true; | ||||
| } | } | ||||
| break; | break; | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| public function describeAutomaticCapability($capability) { | public function describeAutomaticCapability($capability) { | ||||
| // TODO: Clarify the additional rules that parent and subprojects imply. | |||||
| switch ($capability) { | switch ($capability) { | ||||
| case PhabricatorPolicyCapability::CAN_VIEW: | case PhabricatorPolicyCapability::CAN_VIEW: | ||||
| return pht('Members of a project can always view it.'); | return pht('Members of a project can always view it.'); | ||||
| case PhabricatorPolicyCapability::CAN_JOIN: | case PhabricatorPolicyCapability::CAN_JOIN: | ||||
| return pht('Users who can edit a project can always join it.'); | return pht('Users who can edit a project can always join it.'); | ||||
| } | } | ||||
| return null; | 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) { | public function isUserMember($user_phid) { | ||||
| if ($this->memberPHIDs !== self::ATTACHABLE) { | if ($this->memberPHIDs !== self::ATTACHABLE) { | ||||
| return in_array($user_phid, $this->memberPHIDs); | return in_array($user_phid, $this->memberPHIDs); | ||||
| } | } | ||||
| return $this->assertAttachedKey($this->sparseMembers, $user_phid); | return $this->assertAttachedKey($this->sparseMembers, $user_phid); | ||||
| } | } | ||||
| public function setIsUserMember($user_phid, $is_member) { | public function setIsUserMember($user_phid, $is_member) { | ||||
| Show All 17 Lines | return array( | ||||
| 'color' => 'text32', | 'color' => 'text32', | ||||
| 'mailKey' => 'bytes20', | 'mailKey' => 'bytes20', | ||||
| 'joinPolicy' => 'policy', | 'joinPolicy' => 'policy', | ||||
| 'parentProjectPHID' => 'phid?', | 'parentProjectPHID' => 'phid?', | ||||
| 'hasWorkboard' => 'bool', | 'hasWorkboard' => 'bool', | ||||
| 'hasMilestones' => 'bool', | 'hasMilestones' => 'bool', | ||||
| 'hasSubprojects' => 'bool', | 'hasSubprojects' => 'bool', | ||||
| 'milestoneNumber' => 'uint32?', | 'milestoneNumber' => 'uint32?', | ||||
| 'projectPath' => 'hashpath64', | |||||
| 'projectDepth' => 'uint32', | |||||
| ), | ), | ||||
| self::CONFIG_KEY_SCHEMA => array( | self::CONFIG_KEY_SCHEMA => array( | ||||
| 'key_phid' => null, | 'key_phid' => null, | ||||
| 'phid' => array( | 'phid' => array( | ||||
| 'columns' => array('phid'), | 'columns' => array('phid'), | ||||
| 'unique' => true, | 'unique' => true, | ||||
| ), | ), | ||||
| 'key_icon' => array( | 'key_icon' => array( | ||||
| Show All 9 Lines | return array( | ||||
| 'key_milestone' => array( | 'key_milestone' => array( | ||||
| 'columns' => array('parentProjectPHID', 'milestoneNumber'), | 'columns' => array('parentProjectPHID', 'milestoneNumber'), | ||||
| 'unique' => true, | 'unique' => true, | ||||
| ), | ), | ||||
| 'key_primaryslug' => array( | 'key_primaryslug' => array( | ||||
| 'columns' => array('primarySlug'), | 'columns' => array('primarySlug'), | ||||
| 'unique' => true, | 'unique' => true, | ||||
| ), | ), | ||||
| 'key_path' => array( | |||||
| 'columns' => array('projectPath', 'projectDepth'), | |||||
| ), | |||||
| ), | ), | ||||
| ) + parent::getConfiguration(); | ) + parent::getConfiguration(); | ||||
| } | } | ||||
| public function generatePHID() { | public function generatePHID() { | ||||
| return PhabricatorPHID::generateNewPHID( | return PhabricatorPHID::generateNewPHID( | ||||
| PhabricatorProjectProjectPHIDType::TYPECONST); | PhabricatorProjectProjectPHIDType::TYPECONST); | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 66 Lines • ▼ Show 20 Lines | public function getColor() { | ||||
| return $this->color; | return $this->color; | ||||
| } | } | ||||
| public function save() { | public function save() { | ||||
| if (!$this->getMailKey()) { | if (!$this->getMailKey()) { | ||||
| $this->setMailKey(Filesystem::readRandomCharacters(20)); | $this->setMailKey(Filesystem::readRandomCharacters(20)); | ||||
| } | } | ||||
| if (!strlen($this->getPHID())) { | |||||
| $this->setPHID($this->generatePHID()); | |||||
| } | |||||
| $path = array(); | |||||
| $depth = 0; | |||||
| if ($this->parentProjectPHID) { | |||||
| $parent = $this->getParentProject(); | |||||
| $path[] = $parent->getProjectPath(); | |||||
| $depth = $parent->getProjectDepth() + 1; | |||||
| } | |||||
| $hash = PhabricatorHash::digestForIndex($this->getPHID()); | |||||
| $path[] = substr($hash, 0, 4); | |||||
| $path = implode('', $path); | |||||
| $limit = self::getProjectDepthLimit(); | |||||
| if (strlen($path) > ($limit * 4)) { | |||||
| throw new Exception( | |||||
| pht('Unable to save project: path length is too long.')); | |||||
| } | |||||
| $this->setProjectPath($path); | |||||
| $this->setProjectDepth($depth); | |||||
| $this->openTransaction(); | $this->openTransaction(); | ||||
| $result = parent::save(); | $result = parent::save(); | ||||
| $this->updateDatasourceTokens(); | $this->updateDatasourceTokens(); | ||||
| $this->saveTransaction(); | $this->saveTransaction(); | ||||
| return $result; | 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() { | public function updateDatasourceTokens() { | ||||
| $table = self::TABLE_DATASOURCE_TOKEN; | $table = self::TABLE_DATASOURCE_TOKEN; | ||||
| $conn_w = $this->establishConnection('w'); | $conn_w = $this->establishConnection('w'); | ||||
| $id = $this->getID(); | $id = $this->getID(); | ||||
| $slugs = queryfx_all( | $slugs = queryfx_all( | ||||
| $conn_w, | $conn_w, | ||||
| 'SELECT * FROM %T WHERE projectPHID = %s', | 'SELECT * FROM %T WHERE projectPHID = %s', | ||||
| Show All 31 Lines | final class PhabricatorProject extends PhabricatorProjectDAO | ||||
| public function isMilestone() { | public function isMilestone() { | ||||
| return ($this->getMilestoneNumber() !== null); | return ($this->getMilestoneNumber() !== null); | ||||
| } | } | ||||
| public function getParentProject() { | public function getParentProject() { | ||||
| return $this->assertAttached($this->parentProject); | return $this->assertAttached($this->parentProject); | ||||
| } | } | ||||
| public function attachParentProject(PhabricatorProject $project) { | public function attachParentProject(PhabricatorProject $project = null) { | ||||
| $this->parentProject = $project; | $this->parentProject = $project; | ||||
| return $this; | 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; | |||||
| } | |||||
| /* -( PhabricatorSubscribableInterface )----------------------------------- */ | /* -( PhabricatorSubscribableInterface )----------------------------------- */ | ||||
| public function isAutomaticallySubscribed($phid) { | public function isAutomaticallySubscribed($phid) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 87 Lines • Show Last 20 Lines | |||||