diff --git a/resources/sql/patches/20130310.xactionmeta.sql b/resources/sql/patches/20130310.xactionmeta.sql new file mode 100644 index 0000000000..e1e1d13fa9 --- /dev/null +++ b/resources/sql/patches/20130310.xactionmeta.sql @@ -0,0 +1,24 @@ +ALTER TABLE `{$NAMESPACE}_file`.`macro_transaction` + ADD metadata LONGTEXT NOT NULL COLLATE utf8_bin; +UPDATE `{$NAMESPACE}_file`.macro_transaction SET metadata = '{}' + WHERE metadata = ''; + +ALTER TABLE `{$NAMESPACE}_pholio`.`pholio_transaction` + ADD metadata LONGTEXT NOT NULL COLLATE utf8_bin; +UPDATE `{$NAMESPACE}_pholio`.pholio_transaction SET metadata = '{}' + WHERE metadata = ''; + +ALTER TABLE `{$NAMESPACE}_config`.`config_transaction` + ADD metadata LONGTEXT NOT NULL COLLATE utf8_bin; +UPDATE `{$NAMESPACE}_config`.config_transaction SET metadata = '{}' + WHERE metadata = ''; + +ALTER TABLE `{$NAMESPACE}_conpherence`.`conpherence_transaction` + ADD metadata LONGTEXT NOT NULL COLLATE utf8_bin; +UPDATE `{$NAMESPACE}_conpherence`.conpherence_transaction SET metadata = '{}' + WHERE metadata = ''; + +ALTER TABLE `{$NAMESPACE}_phlux`.`phlux_transaction` + ADD metadata LONGTEXT NOT NULL COLLATE utf8_bin; +UPDATE `{$NAMESPACE}_phlux`.phlux_transaction SET metadata = '{}' + WHERE metadata = ''; diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 292a369851..98c60685fe 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -192,7 +192,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return $v; case ConpherenceTransactionType::TYPE_FILES: case ConpherenceTransactionType::TYPE_PARTICIPANTS: - return $this->mergePHIDTransactions($u, $v); + return $this->mergePHIDOrEdgeTransactions($u, $v); } return parent::mergeTransactions($u, $v); diff --git a/src/applications/transactions/constants/PhabricatorTransactions.php b/src/applications/transactions/constants/PhabricatorTransactions.php index c70b185fef..a71f0e556f 100644 --- a/src/applications/transactions/constants/PhabricatorTransactions.php +++ b/src/applications/transactions/constants/PhabricatorTransactions.php @@ -6,6 +6,7 @@ final class PhabricatorTransactions { const TYPE_SUBSCRIBERS = 'core:subscribers'; const TYPE_VIEW_POLICY = 'core:view-policy'; const TYPE_EDIT_POLICY = 'core:edit-policy'; + const TYPE_EDGE = 'core:edge'; const COLOR_RED = 'red'; const COLOR_ORANGE = 'orange'; diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index f9f51f1d8f..525a90f156 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -101,6 +101,26 @@ abstract class PhabricatorApplicationTransactionEditor return $object->getViewPolicy(); case PhabricatorTransactions::TYPE_EDIT_POLICY: return $object->getEditPolicy(); + case PhabricatorTransactions::TYPE_EDGE: + $edge_type = $xaction->getMetadataValue('edge:type'); + if (!$edge_type) { + throw new Exception("Edge transaction has no 'edge:type'!"); + } + + $old_edges = array(); + if ($object->getPHID()) { + $edge_src = $object->getPHID(); + + $old_edges = id(new PhabricatorEdgeQuery()) + ->setViewer($this->getActor()) + ->withSourcePHIDs(array($edge_src)) + ->withEdgeTypes(array($edge_type)) + ->needEdgeData(true) + ->execute(); + + $old_edges = $old_edges[$edge_src][$edge_type]; + } + return $old_edges; default: return $this->getCustomTransactionOldValue($object, $xaction); } @@ -115,6 +135,8 @@ abstract class PhabricatorApplicationTransactionEditor case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: return $xaction->getNewValue(); + case PhabricatorTransactions::TYPE_EDGE: + return $this->getEdgeTransactionNewValue($xaction); default: return $this->getCustomTransactionNewValue($object, $xaction); } @@ -180,7 +202,46 @@ abstract class PhabricatorApplicationTransactionEditor $subeditor->save(); break; + case PhabricatorTransactions::TYPE_EDGE: + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + $src = $object->getPHID(); + $type = $xaction->getMetadataValue('edge:type'); + + foreach ($new as $dst_phid => $edge) { + $new[$dst_phid]['src'] = $src; + } + + $editor = id(new PhabricatorEdgeEditor()) + ->setActor($this->getActor()); + + foreach ($old as $dst_phid => $edge) { + if (!empty($new[$dst_phid])) { + if ($old[$dst_phid]['data'] === $new[$dst_phid]['data']) { + continue; + } + } + $editor->removeEdge($src, $type, $dst_phid); + } + + foreach ($new as $dst_phid => $edge) { + if (!empty($old[$dst_phid])) { + if ($old[$dst_phid]['data'] === $new[$dst_phid]['data']) { + continue; + } + } + + $data = array( + 'data' => $edge['data'], + ); + + $editor->addEdge($src, $type, $dst_phid, $data); + } + + $editor->save(); + break; } + return $this->applyCustomExternalTransaction($object, $xaction); } @@ -460,7 +521,14 @@ abstract class PhabricatorApplicationTransactionEditor switch ($type) { case PhabricatorTransactions::TYPE_SUBSCRIBERS: - return $this->mergePHIDTransactions($u, $v); + return $this->mergePHIDOrEdgeTransactions($u, $v); + case PhabricatorTransactions::TYPE_EDGE: + $u_type = $u->getMetadataValue('edge:type'); + $v_type = $v->getMetadataValue('edge:type'); + if ($u_type == $v_type) { + return $this->mergePHIDOrEdgeTransactions($u, $v); + } + return null; } // By default, do not merge the transactions. @@ -516,7 +584,7 @@ abstract class PhabricatorApplicationTransactionEditor return array_values($result); } - protected function mergePHIDTransactions( + protected function mergePHIDOrEdgeTransactions( PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { @@ -529,7 +597,6 @@ abstract class PhabricatorApplicationTransactionEditor return $u; } - protected function getPHIDTransactionNewValue( PhabricatorApplicationTransaction $xaction) { @@ -578,6 +645,110 @@ abstract class PhabricatorApplicationTransactionEditor return array_values($result); } + protected function getEdgeTransactionNewValue( + PhabricatorApplicationTransaction $xaction) { + + $new = $xaction->getNewValue(); + $new_add = idx($new, '+', array()); + unset($new['+']); + $new_rem = idx($new, '-', array()); + unset($new['-']); + $new_set = idx($new, '=', null); + unset($new['=']); + + if ($new) { + throw new Exception( + "Invalid 'new' value for Edge transaction. Value should contain only ". + "keys '+' (add edges), '-' (remove edges) and '=' (set edges)."); + } + + $old = $xaction->getOldValue(); + + $lists = array($new_set, $new_add, $new_rem); + foreach ($lists as $list) { + $this->checkEdgeList($list); + } + + $result = array(); + foreach ($old as $dst_phid => $edge) { + if ($new_set !== null && empty($new_set[$dst_phid])) { + continue; + } + $result[$dst_phid] = $this->normalizeEdgeTransactionValue( + $xaction, + $edge); + } + + if ($new_set !== null) { + foreach ($new_set as $dst_phid => $edge) { + $result[$dst_phid] = $this->normalizeEdgeTransactionValue( + $xaction, + $edge); + } + } + + foreach ($new_add as $dst_phid => $edge) { + $result[$dst_phid] = $this->normalizeEdgeTransactionValue( + $xaction, + $edge); + } + + foreach ($new_rem as $dst_phid => $edge) { + unset($result[$dst_phid]); + } + + return $result; + } + + private function checkEdgeList($list) { + if (!$list) { + return; + } + foreach ($list as $key => $item) { + if (phid_get_type($key) === PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { + throw new Exception( + "Edge transactions must have destination PHIDs as in edge ". + "lists (found key '{$key}')."); + } + if (!is_array($item) && $item !== $key) { + throw new Exception( + "Edge transactions must have PHIDs or edge specs as values ". + "(found value '{$item}')."); + } + } + } + + protected function normalizeEdgeTransactionValue( + PhabricatorApplicationTransaction $xaction, + $edge) { + + if (!is_array($edge)) { + $edge = array( + 'dst' => $edge, + ); + } + + $edge_type = $xaction->getMetadataValue('edge:type'); + + if (empty($edge['type'])) { + $edge['type'] = $edge_type; + } else { + if ($edge['type'] != $edge_type) { + $this_type = $edge['type']; + throw new Exception( + "Edge transaction includes edge of type '{$this_type}', but ". + "transaction is of type '{$edge_type}'. Each edge transaction must ". + "alter edges of only one type."); + } + } + + if (!isset($edge['data'])) { + $edge['data'] = null; + } + + return $edge; + } + protected function sortTransactions(array $xactions) { $head = array(); $tail = array(); diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index 50251214fc..ddd678dd18 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -18,6 +18,7 @@ abstract class PhabricatorApplicationTransaction protected $transactionType; protected $oldValue; protected $newValue; + protected $metadata = array(); protected $contentSource; @@ -36,6 +37,15 @@ abstract class PhabricatorApplicationTransaction return new PhabricatorApplicationTransactionView(); } + public function getMetadataValue($key, $default = null) { + return idx($this->metadata, $key, $default); + } + + public function setMetadataValue($key, $value) { + $this->metadata[$key] = $value; + return $this; + } + public function generatePHID() { $type = PhabricatorPHIDConstants::PHID_TYPE_XACT; $subtype = $this->getApplicationTransactionType(); @@ -49,6 +59,7 @@ abstract class PhabricatorApplicationTransaction self::CONFIG_SERIALIZATION => array( 'oldValue' => self::SERIALIZATION_JSON, 'newValue' => self::SERIALIZATION_JSON, + 'metadata' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } @@ -108,6 +119,10 @@ abstract class PhabricatorApplicationTransaction $phids[] = $old; $phids[] = $new; break; + case PhabricatorTransactions::TYPE_EDGE: + $phids[] = ipull($old, 'dst'); + $phids[] = ipull($new, 'dst'); + break; } return array_mergev($phids); @@ -160,6 +175,8 @@ abstract class PhabricatorApplicationTransaction case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: return 'lock'; + case PhabricatorTransactions::TYPE_EDGE: + return 'link'; } return null; @@ -201,6 +218,8 @@ abstract class PhabricatorApplicationTransaction return pht( 'All users are already subscribed to this %s.', $this->getApplicationObjectTypeName()); + case PhabricatorTransactions::TYPE_EDGE: + return pht('Edges already exist; transaction has no effect.'); } return pht('Transaction has no effect.'); @@ -259,6 +278,12 @@ abstract class PhabricatorApplicationTransaction $this->renderHandleList($rem)); } break; + case PhabricatorTransactions::TYPE_EDGE: + $type = $this->getMetadata('edge:type'); + return pht( + '%s edited edges of type %s.', + $this->renderHandleLink($author_phid), + $type); default: return pht( '%s edited this %s.', @@ -295,6 +320,11 @@ abstract class PhabricatorApplicationTransaction '%s updated subscribers of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); + case PhabricatorTransactions::TYPE_EDGE: + return pht( + '%s updated edges of %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); } return $this->getTitle(); diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index 803fa1dfbf..0bd10daba4 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -1194,6 +1194,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList { 'type' => 'sql', 'name' => $this->getPatchPath('20130317.phrictionedge.sql'), ), + '20130310.xactionmeta.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20130310.xactionmeta.sql'), + ), ); }