Differential D15167 Diff 36619 src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
Show First 20 Lines • Show All 394 Lines • ▼ Show 20 Lines | switch ($xaction->getTransactionType()) { | ||||
// to read the space PHID from the request. | // to read the space PHID from the request. | ||||
// Just make this work like callers might reasonably expect so we | // Just make this work like callers might reasonably expect so we | ||||
// don't need to handle this specially in every EditController. | // don't need to handle this specially in every EditController. | ||||
return $this->getActor()->getDefaultSpacePHID(); | return $this->getActor()->getDefaultSpacePHID(); | ||||
} else { | } else { | ||||
return $space_phid; | return $space_phid; | ||||
} | } | ||||
case PhabricatorTransactions::TYPE_EDGE: | case PhabricatorTransactions::TYPE_EDGE: | ||||
return $this->getEdgeTransactionNewValue($xaction); | $new_value = $this->getEdgeTransactionNewValue($xaction); | ||||
$edge_type = $xaction->getMetadataValue('edge:type'); | |||||
$type_project = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; | |||||
if ($edge_type == $type_project) { | |||||
$new_value = $this->applyProjectConflictRules($new_value); | |||||
} | |||||
return $new_value; | |||||
case PhabricatorTransactions::TYPE_CUSTOMFIELD: | case PhabricatorTransactions::TYPE_CUSTOMFIELD: | ||||
$field = $this->getCustomFieldForTransaction($object, $xaction); | $field = $this->getCustomFieldForTransaction($object, $xaction); | ||||
return $field->getNewValueFromApplicationTransactions($xaction); | return $field->getNewValueFromApplicationTransactions($xaction); | ||||
case PhabricatorTransactions::TYPE_COMMENT: | case PhabricatorTransactions::TYPE_COMMENT: | ||||
return null; | return null; | ||||
default: | default: | ||||
return $this->getCustomTransactionNewValue($object, $xaction); | return $this->getCustomTransactionNewValue($object, $xaction); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 2,929 Lines • ▼ Show 20 Lines | foreach ($state as $key => $value) { | ||||
break; | break; | ||||
} | } | ||||
$state[$key] = $value; | $state[$key] = $value; | ||||
} | } | ||||
return $state; | return $state; | ||||
} | } | ||||
/** | |||||
* Remove conflicts from a list of projects. | |||||
* | |||||
* Objects aren't allowed to be tagged with multiple milestones in the same | |||||
* group, nor projects such that one tag is the ancestor of any other tag. | |||||
* If the list of PHIDs include mutually exclusive projects, remove the | |||||
* conflicting projects. | |||||
* | |||||
* @param list<phid> List of project PHIDs. | |||||
* @return list<phid> List with conflicts removed. | |||||
*/ | |||||
private function applyProjectConflictRules(array $phids) { | |||||
if (!$phids) { | |||||
return array(); | |||||
} | |||||
// Overall, the last project in the list wins in cases of conflict (so when | |||||
// you add something, the thing you just added sticks and removes older | |||||
// values). | |||||
// Beyond that, there are two basic cases: | |||||
// Milestones: An object can't be in "A > Sprint 3" and "A > Sprint 4". | |||||
// If multiple projects are milestones of the same parent, we only keep the | |||||
// last one. | |||||
// Ancestor: You can't be in "A" and "A > B". If "A > B" comes later | |||||
// in the list, we remove "A" and keep "A > B". If "A" comes later, we | |||||
// remove "A > B" and keep "A". | |||||
// Note that it's OK to be in "A > B" and "A > C". There's only a conflict | |||||
// if one project is an ancestor of another. It's OK to have something | |||||
// tagged with multiple projects which share a common ancestor, so long as | |||||
// they are not mutual ancestors. | |||||
$viewer = PhabricatorUser::getOmnipotentUser(); | |||||
$projects = id(new PhabricatorProjectQuery()) | |||||
->setViewer($viewer) | |||||
->withPHIDs(array_keys($phids)) | |||||
->execute(); | |||||
$projects = mpull($projects, null, 'getPHID'); | |||||
// We're going to build a map from each project with milestones to the last | |||||
// milestone in the list. This last milestone is the milestone we'll keep. | |||||
$milestone_map = array(); | |||||
// We're going to build a set of the projects which have no descendants | |||||
// later in the list. This allows us to apply both ancestor rules. | |||||
$ancestor_map = array(); | |||||
foreach ($phids as $phid => $ignored) { | |||||
$project = idx($projects, $phid); | |||||
if (!$project) { | |||||
continue; | |||||
} | |||||
// This is the last milestone we've seen, so set it as the selection for | |||||
// the project's parent. This might be setting a new value or overwriting | |||||
// an earlier value. | |||||
if ($project->isMilestone()) { | |||||
$parent_phid = $project->getParentProjectPHID(); | |||||
$milestone_map[$parent_phid] = $phid; | |||||
} | |||||
// Since this is the last item in the list we've examined so far, add it | |||||
// to the set of projects with no later descendants. | |||||
$ancestor_map[$phid] = $phid; | |||||
// Remove any ancestors from the set, since this is a later descendant. | |||||
foreach ($project->getAncestorProjects() as $ancestor) { | |||||
$ancestor_phid = $ancestor->getPHID(); | |||||
unset($ancestor_map[$ancestor_phid]); | |||||
} | |||||
} | |||||
// Now that we've built the maps, we can throw away all the projects which | |||||
// have conflicts. | |||||
foreach ($phids as $phid => $ignored) { | |||||
$project = idx($projects, $phid); | |||||
if (!$project) { | |||||
// If a PHID is invalid, we just leave it as-is. We could clean it up, | |||||
// but leaving it untouched is less likely to cause collateral damage. | |||||
continue; | |||||
} | |||||
// If this was a milestone, check if it was the last milestone from its | |||||
// group in the list. If not, remove it from the list. | |||||
if ($project->isMilestone()) { | |||||
$parent_phid = $project->getParentProjectPHID(); | |||||
if ($milestone_map[$parent_phid] !== $phid) { | |||||
unset($phids[$phid]); | |||||
continue; | |||||
} | |||||
} | |||||
// If a later project in the list is a subproject of this one, it will | |||||
// have removed ancestors from the map. If this project does not point | |||||
// at itself in the ancestor map, it should be discarded in favor of a | |||||
// subproject that comes later. | |||||
if (idx($ancestor_map, $phid) !== $phid) { | |||||
unset($phids[$phid]); | |||||
continue; | |||||
} | |||||
// If a later project in the list is an ancestor of this one, it will | |||||
// have added itself to the map. If any ancestor of this project points | |||||
// at itself in the map, this project should be dicarded in favor of | |||||
// that later ancestor. | |||||
foreach ($project->getAncestorProjects() as $ancestor) { | |||||
$ancestor_phid = $ancestor->getPHID(); | |||||
if (isset($ancestor_map[$ancestor_phid])) { | |||||
unset($phids[$phid]); | |||||
continue 2; | |||||
} | |||||
} | |||||
} | |||||
return $phids; | |||||
} | |||||
} | } |