Differential D19969 Diff 47761 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 441 Lines • ▼ Show 20 Lines | switch ($type) { | ||||
$edge_type = $xaction->getMetadataValue('edge:type'); | $edge_type = $xaction->getMetadataValue('edge:type'); | ||||
if (!$edge_type) { | if (!$edge_type) { | ||||
throw new Exception( | throw new Exception( | ||||
pht( | pht( | ||||
"Edge transaction has no '%s'!", | "Edge transaction has no '%s'!", | ||||
'edge:type')); | 'edge:type')); | ||||
} | } | ||||
// See T13082. If this is an inverse edit, the parent editor has | |||||
// already populated the transaction values correctly. | |||||
if ($this->getIsInverseEdgeEditor()) { | |||||
return $xaction->getOldValue(); | |||||
} | |||||
$old_edges = array(); | $old_edges = array(); | ||||
if ($object->getPHID()) { | if ($object->getPHID()) { | ||||
$edge_src = $object->getPHID(); | $edge_src = $object->getPHID(); | ||||
$old_edges = id(new PhabricatorEdgeQuery()) | $old_edges = id(new PhabricatorEdgeQuery()) | ||||
->withSourcePHIDs(array($edge_src)) | ->withSourcePHIDs(array($edge_src)) | ||||
->withEdgeTypes(array($edge_type)) | ->withEdgeTypes(array($edge_type)) | ||||
->needEdgeData(true) | ->needEdgeData(true) | ||||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | switch ($type) { | ||||
// 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: | ||||
// See T13082. If this is an inverse edit, the parent editor has | |||||
// already populated appropriate transaction values. | |||||
if ($this->getIsInverseEdgeEditor()) { | |||||
return $xaction->getNewValue(); | |||||
} | |||||
$new_value = $this->getEdgeTransactionNewValue($xaction); | $new_value = $this->getEdgeTransactionNewValue($xaction); | ||||
$edge_type = $xaction->getMetadataValue('edge:type'); | $edge_type = $xaction->getMetadataValue('edge:type'); | ||||
$type_project = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; | $type_project = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; | ||||
if ($edge_type == $type_project) { | if ($edge_type == $type_project) { | ||||
$new_value = $this->applyProjectConflictRules($new_value); | $new_value = $this->applyProjectConflictRules($new_value); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 261 Lines • ▼ Show 20 Lines | switch ($xaction->getTransactionType()) { | ||||
break; | break; | ||||
} | } | ||||
$old = $xaction->getOldValue(); | $old = $xaction->getOldValue(); | ||||
$new = $xaction->getNewValue(); | $new = $xaction->getNewValue(); | ||||
$src = $object->getPHID(); | $src = $object->getPHID(); | ||||
$const = $xaction->getMetadataValue('edge:type'); | $const = $xaction->getMetadataValue('edge:type'); | ||||
$type = PhabricatorEdgeType::getByConstant($const); | |||||
if ($type->shouldWriteInverseTransactions()) { | |||||
$this->applyInverseEdgeTransactions( | |||||
$object, | |||||
$xaction, | |||||
$type->getInverseEdgeConstant()); | |||||
} | |||||
foreach ($new as $dst_phid => $edge) { | foreach ($new as $dst_phid => $edge) { | ||||
$new[$dst_phid]['src'] = $src; | $new[$dst_phid]['src'] = $src; | ||||
} | } | ||||
$editor = new PhabricatorEdgeEditor(); | $editor = new PhabricatorEdgeEditor(); | ||||
foreach ($old as $dst_phid => $edge) { | foreach ($old as $dst_phid => $edge) { | ||||
if (!empty($new[$dst_phid])) { | if (!empty($new[$dst_phid])) { | ||||
▲ Show 20 Lines • Show All 86 Lines • ▼ Show 20 Lines | abstract class PhabricatorApplicationTransactionEditor | ||||
final protected function didCommitTransactions( | final protected function didCommitTransactions( | ||||
PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
array $xactions) { | array $xactions) { | ||||
foreach ($xactions as $xaction) { | foreach ($xactions as $xaction) { | ||||
$type = $xaction->getTransactionType(); | $type = $xaction->getTransactionType(); | ||||
// See T13082. When we're writing edges that imply corresponding inverse | |||||
// transactions, apply those inverse transactions now. We have to wait | |||||
// until the object we're editing (with this editor) has committed its | |||||
// transactions to do this. If we don't, the inverse editor may race, | |||||
// build a mail before we actually commit this object, and render "alice | |||||
// added an edge: Unknown Object". | |||||
if ($type === PhabricatorTransactions::TYPE_EDGE) { | |||||
// Don't do anything if we're already an inverse edge editor. | |||||
if ($this->getIsInverseEdgeEditor()) { | |||||
continue; | |||||
} | |||||
$edge_const = $xaction->getMetadataValue('edge:type'); | |||||
$edge_type = PhabricatorEdgeType::getByConstant($edge_const); | |||||
if ($edge_type->shouldWriteInverseTransactions()) { | |||||
$this->applyInverseEdgeTransactions( | |||||
$object, | |||||
$xaction, | |||||
$edge_type->getInverseEdgeConstant()); | |||||
} | |||||
continue; | |||||
} | |||||
$xtype = $this->getModularTransactionType($type); | $xtype = $this->getModularTransactionType($type); | ||||
if (!$xtype) { | if (!$xtype) { | ||||
continue; | continue; | ||||
} | } | ||||
$xtype = clone $xtype; | $xtype = clone $xtype; | ||||
$xtype->setStorage($xaction); | $xtype->setStorage($xaction); | ||||
$xtype->didCommitTransaction($object, $xaction->getNewValue()); | $xtype->didCommitTransaction($object, $xaction->getNewValue()); | ||||
▲ Show 20 Lines • Show All 588 Lines • ▼ Show 20 Lines | foreach ($xactions as $xaction) { | ||||
pht( | pht( | ||||
'You can not apply transactions which already have '. | 'You can not apply transactions which already have '. | ||||
'commentVersions!')); | 'commentVersions!')); | ||||
} | } | ||||
$expect_value = !$xaction->shouldGenerateOldValue(); | $expect_value = !$xaction->shouldGenerateOldValue(); | ||||
$has_value = $xaction->hasOldValue(); | $has_value = $xaction->hasOldValue(); | ||||
// See T13082. In the narrow case of applying inverse edge edits, we | |||||
// expect the old value to be populated. | |||||
if ($this->getIsInverseEdgeEditor()) { | |||||
$expect_value = true; | |||||
} | |||||
if ($expect_value && !$has_value) { | if ($expect_value && !$has_value) { | ||||
throw new PhabricatorApplicationTransactionStructureException( | throw new PhabricatorApplicationTransactionStructureException( | ||||
$xaction, | $xaction, | ||||
pht( | pht( | ||||
'This transaction is supposed to have an %s set, but it does not!', | 'This transaction is supposed to have an %s set, but it does not!', | ||||
'oldValue')); | 'oldValue')); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 2,333 Lines • ▼ Show 20 Lines | private function applyInverseEdgeTransactions( | ||||
$rem = array_fuse($rem); | $rem = array_fuse($rem); | ||||
$all = $add + $rem; | $all = $add + $rem; | ||||
$nodes = id(new PhabricatorObjectQuery()) | $nodes = id(new PhabricatorObjectQuery()) | ||||
->setViewer($this->requireActor()) | ->setViewer($this->requireActor()) | ||||
->withPHIDs($all) | ->withPHIDs($all) | ||||
->execute(); | ->execute(); | ||||
$object_phid = $object->getPHID(); | |||||
foreach ($nodes as $node) { | foreach ($nodes as $node) { | ||||
if (!($node instanceof PhabricatorApplicationTransactionInterface)) { | if (!($node instanceof PhabricatorApplicationTransactionInterface)) { | ||||
continue; | continue; | ||||
} | } | ||||
if ($node instanceof PhabricatorUser) { | if ($node instanceof PhabricatorUser) { | ||||
// TODO: At least for now, don't record inverse edge transactions | // TODO: At least for now, don't record inverse edge transactions | ||||
// for users (for example, "alincoln joined project X"): Feed fills | // for users (for example, "alincoln joined project X"): Feed fills | ||||
// this role instead. | // this role instead. | ||||
continue; | continue; | ||||
} | } | ||||
$node_phid = $node->getPHID(); | |||||
$editor = $node->getApplicationTransactionEditor(); | $editor = $node->getApplicationTransactionEditor(); | ||||
$template = $node->getApplicationTransactionTemplate(); | $template = $node->getApplicationTransactionTemplate(); | ||||
if (isset($add[$node->getPHID()])) { | // See T13082. We have to build these transactions with synthetic values | ||||
$edge_edit_type = '+'; | // because we've already applied the actual edit to the edge database | ||||
// table. If we try to apply this transaction naturally, it will no-op | |||||
// itself because it doesn't have any effect. | |||||
$edge_query = id(new PhabricatorEdgeQuery()) | |||||
->withSourcePHIDs(array($node_phid)) | |||||
->withEdgeTypes(array($inverse_type)); | |||||
$edge_query->execute(); | |||||
$edge_phids = $edge_query->getDestinationPHIDs(); | |||||
$edge_phids = array_fuse($edge_phids); | |||||
$new_phids = $edge_phids; | |||||
$old_phids = $edge_phids; | |||||
if (isset($add[$node_phid])) { | |||||
unset($old_phids[$object_phid]); | |||||
} else { | } else { | ||||
$edge_edit_type = '-'; | $old_phids[$object_phid] = $object_phid; | ||||
} | } | ||||
$template | $template | ||||
->setTransactionType($xaction->getTransactionType()) | ->setTransactionType($xaction->getTransactionType()) | ||||
->setMetadataValue('edge:type', $inverse_type) | ->setMetadataValue('edge:type', $inverse_type) | ||||
->setNewValue( | ->setOldValue($old_phids) | ||||
array( | ->setNewValue($new_phids); | ||||
$edge_edit_type => array($object->getPHID() => $object->getPHID()), | |||||
)); | |||||
$editor | $editor | ||||
->setContinueOnNoEffect(true) | ->setContinueOnNoEffect(true) | ||||
->setContinueOnMissingFields(true) | ->setContinueOnMissingFields(true) | ||||
->setParentMessageID($this->getParentMessageID()) | ->setParentMessageID($this->getParentMessageID()) | ||||
->setIsInverseEdgeEditor(true) | ->setIsInverseEdgeEditor(true) | ||||
->setIsSilent($this->getIsSilent()) | ->setIsSilent($this->getIsSilent()) | ||||
->setActor($this->requireActor()) | ->setActor($this->requireActor()) | ||||
▲ Show 20 Lines • Show All 1,011 Lines • Show Last 20 Lines |