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 |