Differential D14904 Diff 36033 src/applications/project/editor/PhabricatorProjectTransactionEditor.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/project/editor/PhabricatorProjectTransactionEditor.php
| Show First 20 Lines • Show All 68 Lines • ▼ Show 20 Lines | protected function getCustomTransactionNewValue( | ||||
| switch ($xaction->getTransactionType()) { | switch ($xaction->getTransactionType()) { | ||||
| case PhabricatorProjectTransaction::TYPE_NAME: | case PhabricatorProjectTransaction::TYPE_NAME: | ||||
| case PhabricatorProjectTransaction::TYPE_STATUS: | case PhabricatorProjectTransaction::TYPE_STATUS: | ||||
| case PhabricatorProjectTransaction::TYPE_IMAGE: | case PhabricatorProjectTransaction::TYPE_IMAGE: | ||||
| case PhabricatorProjectTransaction::TYPE_ICON: | case PhabricatorProjectTransaction::TYPE_ICON: | ||||
| case PhabricatorProjectTransaction::TYPE_COLOR: | case PhabricatorProjectTransaction::TYPE_COLOR: | ||||
| case PhabricatorProjectTransaction::TYPE_LOCKED: | case PhabricatorProjectTransaction::TYPE_LOCKED: | ||||
| case PhabricatorProjectTransaction::TYPE_PARENT: | case PhabricatorProjectTransaction::TYPE_PARENT: | ||||
| case PhabricatorProjectTransaction::TYPE_MILESTONE: | |||||
| return $xaction->getNewValue(); | return $xaction->getNewValue(); | ||||
| case PhabricatorProjectTransaction::TYPE_SLUGS: | case PhabricatorProjectTransaction::TYPE_SLUGS: | ||||
| return $this->normalizeSlugs($xaction->getNewValue()); | return $this->normalizeSlugs($xaction->getNewValue()); | ||||
| case PhabricatorProjectTransaction::TYPE_MILESTONE: | |||||
| $current = queryfx_one( | |||||
| $object->establishConnection('w'), | |||||
| 'SELECT MAX(milestoneNumber) n | |||||
| FROM %T | |||||
| WHERE parentProjectPHID = %s', | |||||
| $object->getTableName(), | |||||
| $object->getParentProject()->getPHID()); | |||||
| if (!$current) { | |||||
| $number = 1; | |||||
| } else { | |||||
| $number = (int)$current['n'] + 1; | |||||
| } | |||||
| return $number; | |||||
| } | } | ||||
| return parent::getCustomTransactionNewValue($object, $xaction); | return parent::getCustomTransactionNewValue($object, $xaction); | ||||
| } | } | ||||
| protected function applyCustomInternalTransaction( | protected function applyCustomInternalTransaction( | ||||
| PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
| PhabricatorApplicationTransaction $xaction) { | PhabricatorApplicationTransaction $xaction) { | ||||
| Show All 20 Lines | switch ($xaction->getTransactionType()) { | ||||
| return; | return; | ||||
| case PhabricatorProjectTransaction::TYPE_LOCKED: | case PhabricatorProjectTransaction::TYPE_LOCKED: | ||||
| $object->setIsMembershipLocked($xaction->getNewValue()); | $object->setIsMembershipLocked($xaction->getNewValue()); | ||||
| return; | return; | ||||
| case PhabricatorProjectTransaction::TYPE_PARENT: | case PhabricatorProjectTransaction::TYPE_PARENT: | ||||
| $object->setParentProjectPHID($xaction->getNewValue()); | $object->setParentProjectPHID($xaction->getNewValue()); | ||||
| return; | return; | ||||
| case PhabricatorProjectTransaction::TYPE_MILESTONE: | case PhabricatorProjectTransaction::TYPE_MILESTONE: | ||||
| $object->setMilestoneNumber($xaction->getNewValue()); | $current = queryfx_one( | ||||
| $object->establishConnection('w'), | |||||
| 'SELECT MAX(milestoneNumber) n | |||||
| FROM %T | |||||
| WHERE parentProjectPHID = %s', | |||||
| $object->getTableName(), | |||||
| $object->getParentProject()->getPHID()); | |||||
| if (!$current) { | |||||
| $number = 1; | |||||
| } else { | |||||
| $number = (int)$current['n'] + 1; | |||||
| } | |||||
| $object->setMilestoneNumber($number); | |||||
| $object->setParentProjectPHID($xaction->getNewValue()); | |||||
| return; | return; | ||||
| } | } | ||||
| return parent::applyCustomInternalTransaction($object, $xaction); | return parent::applyCustomInternalTransaction($object, $xaction); | ||||
| } | } | ||||
| protected function applyCustomExternalTransaction( | protected function applyCustomExternalTransaction( | ||||
| PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
| ▲ Show 20 Lines • Show All 95 Lines • ▼ Show 20 Lines | switch ($xaction->getTransactionType()) { | ||||
| break; | break; | ||||
| } | } | ||||
| break; | break; | ||||
| } | } | ||||
| return parent::applyBuiltinExternalTransaction($object, $xaction); | return parent::applyBuiltinExternalTransaction($object, $xaction); | ||||
| } | } | ||||
| protected function validateAllTransactions( | |||||
| PhabricatorLiskDAO $object, | |||||
| array $xactions) { | |||||
| $errors = array(); | |||||
| // Prevent creating projects which are both subprojects and milestones, | |||||
| // since this does not make sense, won't work, and will break everything. | |||||
| $parent_xaction = null; | |||||
| foreach ($xactions as $xaction) { | |||||
| switch ($xaction->getTransactionType()) { | |||||
| case PhabricatorProjectTransaction::TYPE_PARENT: | |||||
| case PhabricatorProjectTransaction::TYPE_MILESTONE: | |||||
| if ($xaction->getNewValue() === null) { | |||||
| continue; | |||||
| } | |||||
| if (!$parent_xaction) { | |||||
| $parent_xaction = $xaction; | |||||
| continue; | |||||
| } | |||||
| $errors[] = new PhabricatorApplicationTransactionValidationError( | |||||
| $xaction->getTransactionType(), | |||||
| pht('Invalid'), | |||||
| pht( | |||||
| 'When creating a project, specify a maximum of one parent '. | |||||
| 'project or milestone project. A project can not be both a '. | |||||
| 'subproject and a milestone.'), | |||||
| $xaction); | |||||
| break; | |||||
| break; | |||||
| } | |||||
| } | |||||
| $is_milestone = $object->isMilestone(); | |||||
| foreach ($xactions as $xaction) { | |||||
| switch ($xaction->getTransactionType()) { | |||||
| case PhabricatorProjectTransaction::TYPE_MILESTONE: | |||||
| if ($xaction->getNewValue() !== null) { | |||||
| $is_milestone = true; | |||||
| } | |||||
| break; | |||||
| } | |||||
| } | |||||
| $is_parent = $object->getHasSubprojects(); | |||||
| foreach ($xactions as $xaction) { | |||||
| switch ($xaction->getTransactionType()) { | |||||
| case PhabricatorProjectTransaction::TYPE_MEMBERS: | |||||
| if ($is_parent) { | |||||
| $errors[] = new PhabricatorApplicationTransactionValidationError( | |||||
| $xaction->getTransactionType(), | |||||
| pht('Invalid'), | |||||
| pht( | |||||
| 'You can not change members of a project with subprojects '. | |||||
| 'directly. Members of any subproject are automatically '. | |||||
| 'members of the parent project.'), | |||||
| $xaction); | |||||
| } | |||||
| if ($is_milestone) { | |||||
| $errors[] = new PhabricatorApplicationTransactionValidationError( | |||||
| $xaction->getTransactionType(), | |||||
| pht('Invalid'), | |||||
| pht( | |||||
| 'You can not change members of a milestone. Members of the '. | |||||
| 'parent project are automatically members of the milestone.'), | |||||
| $xaction); | |||||
| } | |||||
| break; | |||||
| } | |||||
| } | |||||
| return $errors; | |||||
| } | |||||
| protected function validateTransaction( | protected function validateTransaction( | ||||
| PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
| $type, | $type, | ||||
| array $xactions) { | array $xactions) { | ||||
| $errors = parent::validateTransaction($object, $type, $xactions); | $errors = parent::validateTransaction($object, $type, $xactions); | ||||
| switch ($type) { | switch ($type) { | ||||
| ▲ Show 20 Lines • Show All 112 Lines • ▼ Show 20 Lines | switch ($type) { | ||||
| phutil_count($used_slug_strs), | phutil_count($used_slug_strs), | ||||
| implode(', ', $used_slug_strs)), | implode(', ', $used_slug_strs)), | ||||
| $slug_xaction); | $slug_xaction); | ||||
| $errors[] = $error; | $errors[] = $error; | ||||
| } | } | ||||
| break; | break; | ||||
| case PhabricatorProjectTransaction::TYPE_PARENT: | case PhabricatorProjectTransaction::TYPE_PARENT: | ||||
| case PhabricatorProjectTransaction::TYPE_MILESTONE: | |||||
| if (!$xactions) { | if (!$xactions) { | ||||
| break; | break; | ||||
| } | } | ||||
| $xaction = last($xactions); | $xaction = last($xactions); | ||||
| $parent_phid = $xaction->getNewValue(); | |||||
| if (!$parent_phid) { | |||||
| continue; | |||||
| } | |||||
| if (!$this->getIsNewObject()) { | if (!$this->getIsNewObject()) { | ||||
| $errors[] = new PhabricatorApplicationTransactionValidationError( | $errors[] = new PhabricatorApplicationTransactionValidationError( | ||||
| $type, | $type, | ||||
| pht('Invalid'), | pht('Invalid'), | ||||
| pht( | pht( | ||||
| 'You can only set a parent project when creating a project '. | 'You can only set a parent or milestone project when creating a '. | ||||
| 'for the first time.'), | 'project for the first time.'), | ||||
| $xaction); | $xaction); | ||||
| break; | break; | ||||
| } | } | ||||
| $parent_phid = $xaction->getNewValue(); | |||||
| $projects = id(new PhabricatorProjectQuery()) | $projects = id(new PhabricatorProjectQuery()) | ||||
| ->setViewer($this->requireActor()) | ->setViewer($this->requireActor()) | ||||
| ->withPHIDs(array($parent_phid)) | ->withPHIDs(array($parent_phid)) | ||||
| ->requireCapabilities( | ->requireCapabilities( | ||||
| array( | array( | ||||
| PhabricatorPolicyCapability::CAN_VIEW, | PhabricatorPolicyCapability::CAN_VIEW, | ||||
| PhabricatorPolicyCapability::CAN_EDIT, | PhabricatorPolicyCapability::CAN_EDIT, | ||||
| )) | )) | ||||
| ->execute(); | ->execute(); | ||||
| if (!$projects) { | if (!$projects) { | ||||
| $errors[] = new PhabricatorApplicationTransactionValidationError( | $errors[] = new PhabricatorApplicationTransactionValidationError( | ||||
| $type, | $type, | ||||
| pht('Invalid'), | pht('Invalid'), | ||||
| pht( | pht( | ||||
| 'Parent project PHID ("%s") must be the PHID of a valid, '. | 'Parent or milestone project PHID ("%s") must be the PHID of a '. | ||||
| 'visible project which you have permission to edit.', | 'valid, visible project which you have permission to edit.', | ||||
| $parent_phid), | $parent_phid), | ||||
| $xaction); | $xaction); | ||||
| break; | break; | ||||
| } | } | ||||
| $project = head($projects); | $project = head($projects); | ||||
| if ($project->isMilestone()) { | if ($project->isMilestone()) { | ||||
| $errors[] = new PhabricatorApplicationTransactionValidationError( | $errors[] = new PhabricatorApplicationTransactionValidationError( | ||||
| $type, | $type, | ||||
| pht('Invalid'), | pht('Invalid'), | ||||
| pht( | pht( | ||||
| 'Parent project PHID ("%s") must not be a milestone. '. | 'Parent or milestone project PHID ("%s") must not be a '. | ||||
| 'Milestones may not have subprojects.', | 'milestone. Milestones may not have subprojects or milestones.', | ||||
| $parent_phid), | $parent_phid), | ||||
| $xaction); | $xaction); | ||||
| break; | break; | ||||
| } | } | ||||
| $limit = PhabricatorProject::getProjectDepthLimit(); | $limit = PhabricatorProject::getProjectDepthLimit(); | ||||
| if ($project->getProjectDepth() >= ($limit - 1)) { | if ($project->getProjectDepth() >= ($limit - 1)) { | ||||
| $errors[] = new PhabricatorApplicationTransactionValidationError( | $errors[] = new PhabricatorApplicationTransactionValidationError( | ||||
| $type, | $type, | ||||
| pht('Invalid'), | pht('Invalid'), | ||||
| pht( | pht( | ||||
| 'You can not create a subproject under this parent because '. | 'You can not create a subproject or mielstone under this parent '. | ||||
| 'it would nest projects too deeply. The maximum nesting '. | 'because it would nest projects too deeply. The maximum '. | ||||
| 'depth of projects is %s.', | 'nesting depth of projects is %s.', | ||||
| new PhutilNumber($limit)), | new PhutilNumber($limit)), | ||||
| $xaction); | $xaction); | ||||
| break; | break; | ||||
| } | } | ||||
| $object->attachParentProject($project); | $object->attachParentProject($project); | ||||
| break; | break; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 165 Lines • ▼ Show 20 Lines | protected function extractFilePHIDsFromCustomTransaction( | ||||
| return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); | return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); | ||||
| } | } | ||||
| protected function applyFinalEffects( | protected function applyFinalEffects( | ||||
| PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
| array $xactions) { | array $xactions) { | ||||
| $materialize = false; | $materialize = false; | ||||
| $new_parent = null; | |||||
| foreach ($xactions as $xaction) { | foreach ($xactions as $xaction) { | ||||
| switch ($xaction->getTransactionType()) { | switch ($xaction->getTransactionType()) { | ||||
| case PhabricatorTransactions::TYPE_EDGE: | case PhabricatorTransactions::TYPE_EDGE: | ||||
| switch ($xaction->getMetadataValue('edge:type')) { | switch ($xaction->getMetadataValue('edge:type')) { | ||||
| case PhabricatorProjectProjectHasMemberEdgeType::EDGECONST: | case PhabricatorProjectProjectHasMemberEdgeType::EDGECONST: | ||||
| $materialize = true; | $materialize = true; | ||||
| break; | break; | ||||
| } | } | ||||
| break; | break; | ||||
| case PhabricatorProjectTransaction::TYPE_PARENT: | case PhabricatorProjectTransaction::TYPE_PARENT: | ||||
| $materialize = true; | $materialize = true; | ||||
| $new_parent = $object->getParentProject(); | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| if ($new_parent) { | |||||
| // If we just created the first subproject of this parent, we want to | |||||
| // copy all of the real members to the subproject. | |||||
| if (!$new_parent->getHasSubprojects()) { | |||||
| $member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; | |||||
| $project_members = PhabricatorEdgeQuery::loadDestinationPHIDs( | |||||
| $new_parent->getPHID(), | |||||
| $member_type); | |||||
| if ($project_members) { | |||||
| $editor = id(new PhabricatorEdgeEditor()); | |||||
| foreach ($project_members as $phid) { | |||||
| $editor->addEdge($object->getPHID(), $member_type, $phid); | |||||
| } | |||||
| $editor->save(); | |||||
| } | |||||
| } | |||||
| } | |||||
| // TODO: We should dump an informational transaction onto the parent | |||||
| // project to show that we created the sub-thing. | |||||
| if ($materialize) { | if ($materialize) { | ||||
| id(new PhabricatorProjectsMembershipIndexEngineExtension()) | id(new PhabricatorProjectsMembershipIndexEngineExtension()) | ||||
| ->rematerialize($object); | ->rematerialize($object); | ||||
| } | } | ||||
| return parent::applyFinalEffects($object, $xactions); | return parent::applyFinalEffects($object, $xactions); | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 146 Lines • Show Last 20 Lines | |||||