Differential D21816 Diff 51995 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 326 Lines • ▼ Show 20 Lines | abstract class PhabricatorApplicationTransactionEditor | ||||
} | } | ||||
public function getTransactionTypes() { | public function getTransactionTypes() { | ||||
$types = array(); | $types = array(); | ||||
$types[] = PhabricatorTransactions::TYPE_CREATE; | $types[] = PhabricatorTransactions::TYPE_CREATE; | ||||
$types[] = PhabricatorTransactions::TYPE_HISTORY; | $types[] = PhabricatorTransactions::TYPE_HISTORY; | ||||
$types[] = PhabricatorTransactions::TYPE_FILE; | |||||
if ($this->object instanceof PhabricatorEditEngineSubtypeInterface) { | if ($this->object instanceof PhabricatorEditEngineSubtypeInterface) { | ||||
$types[] = PhabricatorTransactions::TYPE_SUBTYPE; | $types[] = PhabricatorTransactions::TYPE_SUBTYPE; | ||||
} | } | ||||
if ($this->object instanceof PhabricatorSubscribableInterface) { | if ($this->object instanceof PhabricatorSubscribableInterface) { | ||||
$types[] = PhabricatorTransactions::TYPE_SUBSCRIBERS; | $types[] = PhabricatorTransactions::TYPE_SUBSCRIBERS; | ||||
} | } | ||||
Show All 40 Lines | private function adjustTransactionValues( | ||||
if ($xaction->shouldGenerateOldValue()) { | if ($xaction->shouldGenerateOldValue()) { | ||||
$old = $this->getTransactionOldValue($object, $xaction); | $old = $this->getTransactionOldValue($object, $xaction); | ||||
$xaction->setOldValue($old); | $xaction->setOldValue($old); | ||||
} | } | ||||
$new = $this->getTransactionNewValue($object, $xaction); | $new = $this->getTransactionNewValue($object, $xaction); | ||||
$xaction->setNewValue($new); | $xaction->setNewValue($new); | ||||
// Apply an optional transformation to convert "external" tranaction | |||||
// values (provided by APIs) into "internal" values. | |||||
$old = $xaction->getOldValue(); | |||||
$new = $xaction->getNewValue(); | |||||
$type = $xaction->getTransactionType(); | |||||
$xtype = $this->getModularTransactionType($type); | |||||
if ($xtype) { | |||||
$xtype = clone $xtype; | |||||
$xtype->setStorage($xaction); | |||||
// TODO: Provide a modular hook for modern transactions to do a | |||||
Lint: TODO Comment: This comment has a TODO. | |||||
// transformation. | |||||
list($old, $new) = array($old, $new); | |||||
return; | |||||
} else { | |||||
switch ($type) { | |||||
case PhabricatorTransactions::TYPE_FILE: | |||||
list($old, $new) = $this->newFileTransactionInternalValues( | |||||
$object, | |||||
$xaction, | |||||
$old, | |||||
$new); | |||||
break; | |||||
} | |||||
} | |||||
$xaction->setOldValue($old); | |||||
$xaction->setNewValue($new); | |||||
} | |||||
private function newFileTransactionInternalValues( | |||||
PhabricatorLiskDAO $object, | |||||
PhabricatorApplicationTransaction $xaction, | |||||
$old, | |||||
$new) { | |||||
$old_map = array(); | |||||
if (!$this->getIsNewObject()) { | |||||
$phid = $object->getPHID(); | |||||
$attachment_table = new PhabricatorFileAttachment(); | |||||
$attachment_conn = $attachment_table->establishConnection('w'); | |||||
$rows = queryfx_all( | |||||
$attachment_conn, | |||||
'SELECT filePHID, attachmentMode FROM %R WHERE objectPHID = %s', | |||||
$attachment_table, | |||||
$phid); | |||||
$old_map = ipull($rows, 'attachmentMode', 'filePHID'); | |||||
} | |||||
$new_map = $old_map; | |||||
foreach ($new as $file_phid => $attachment_mode) { | |||||
if ($attachment_mode == PhabricatorFileAttachment::MODE_DETACH) { | |||||
unset($new_map[$file_phid]); | |||||
continue; | |||||
} | |||||
$new_map[$file_phid] = $attachment_mode; | |||||
} | |||||
foreach (array_keys($old_map + $new_map) as $key) { | |||||
if (isset($old_map[$key]) && isset($new_map[$key])) { | |||||
if ($old_map[$key] === $new_map[$key]) { | |||||
unset($old_map[$key]); | |||||
unset($new_map[$key]); | |||||
} | |||||
} | |||||
} | |||||
return array($old_map, $new_map); | |||||
} | } | ||||
private function getTransactionOldValue( | private function getTransactionOldValue( | ||||
PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
PhabricatorApplicationTransaction $xaction) { | PhabricatorApplicationTransaction $xaction) { | ||||
$type = $xaction->getTransactionType(); | $type = $xaction->getTransactionType(); | ||||
▲ Show 20 Lines • Show All 77 Lines • ▼ Show 20 Lines | switch ($type) { | ||||
} | } | ||||
return $old_edges; | return $old_edges; | ||||
case PhabricatorTransactions::TYPE_CUSTOMFIELD: | case PhabricatorTransactions::TYPE_CUSTOMFIELD: | ||||
// NOTE: Custom fields have their old value pre-populated when they are | // NOTE: Custom fields have their old value pre-populated when they are | ||||
// built by PhabricatorCustomFieldList. | // built by PhabricatorCustomFieldList. | ||||
return $xaction->getOldValue(); | return $xaction->getOldValue(); | ||||
case PhabricatorTransactions::TYPE_COMMENT: | case PhabricatorTransactions::TYPE_COMMENT: | ||||
return null; | return null; | ||||
case PhabricatorTransactions::TYPE_FILE: | |||||
return null; | |||||
default: | default: | ||||
return $this->getCustomTransactionOldValue($object, $xaction); | return $this->getCustomTransactionOldValue($object, $xaction); | ||||
} | } | ||||
} | } | ||||
private function getTransactionNewValue( | private function getTransactionNewValue( | ||||
PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
PhabricatorApplicationTransaction $xaction) { | PhabricatorApplicationTransaction $xaction) { | ||||
Show All 15 Lines | switch ($type) { | ||||
case PhabricatorTransactions::TYPE_VIEW_POLICY: | case PhabricatorTransactions::TYPE_VIEW_POLICY: | ||||
case PhabricatorTransactions::TYPE_EDIT_POLICY: | case PhabricatorTransactions::TYPE_EDIT_POLICY: | ||||
case PhabricatorTransactions::TYPE_JOIN_POLICY: | case PhabricatorTransactions::TYPE_JOIN_POLICY: | ||||
case PhabricatorTransactions::TYPE_INTERACT_POLICY: | case PhabricatorTransactions::TYPE_INTERACT_POLICY: | ||||
case PhabricatorTransactions::TYPE_TOKEN: | case PhabricatorTransactions::TYPE_TOKEN: | ||||
case PhabricatorTransactions::TYPE_INLINESTATE: | case PhabricatorTransactions::TYPE_INLINESTATE: | ||||
case PhabricatorTransactions::TYPE_SUBTYPE: | case PhabricatorTransactions::TYPE_SUBTYPE: | ||||
case PhabricatorTransactions::TYPE_HISTORY: | case PhabricatorTransactions::TYPE_HISTORY: | ||||
case PhabricatorTransactions::TYPE_FILE: | |||||
return $xaction->getNewValue(); | return $xaction->getNewValue(); | ||||
case PhabricatorTransactions::TYPE_MFA: | case PhabricatorTransactions::TYPE_MFA: | ||||
return true; | return true; | ||||
case PhabricatorTransactions::TYPE_SPACE: | case PhabricatorTransactions::TYPE_SPACE: | ||||
$space_phid = $xaction->getNewValue(); | $space_phid = $xaction->getNewValue(); | ||||
if (!strlen($space_phid)) { | if (!strlen($space_phid)) { | ||||
// If an install has no Spaces or the Spaces controls are not visible | // If an install has no Spaces or the Spaces controls are not visible | ||||
// to the viewer, we might end up with the empty string here instead | // to the viewer, we might end up with the empty string here instead | ||||
▲ Show 20 Lines • Show All 142 Lines • ▼ Show 20 Lines | switch ($type) { | ||||
case PhabricatorTransactions::TYPE_EDIT_POLICY: | case PhabricatorTransactions::TYPE_EDIT_POLICY: | ||||
case PhabricatorTransactions::TYPE_JOIN_POLICY: | case PhabricatorTransactions::TYPE_JOIN_POLICY: | ||||
case PhabricatorTransactions::TYPE_INTERACT_POLICY: | case PhabricatorTransactions::TYPE_INTERACT_POLICY: | ||||
case PhabricatorTransactions::TYPE_SUBSCRIBERS: | case PhabricatorTransactions::TYPE_SUBSCRIBERS: | ||||
case PhabricatorTransactions::TYPE_INLINESTATE: | case PhabricatorTransactions::TYPE_INLINESTATE: | ||||
case PhabricatorTransactions::TYPE_EDGE: | case PhabricatorTransactions::TYPE_EDGE: | ||||
case PhabricatorTransactions::TYPE_SPACE: | case PhabricatorTransactions::TYPE_SPACE: | ||||
case PhabricatorTransactions::TYPE_COMMENT: | case PhabricatorTransactions::TYPE_COMMENT: | ||||
case PhabricatorTransactions::TYPE_FILE: | |||||
return $this->applyBuiltinInternalTransaction($object, $xaction); | return $this->applyBuiltinInternalTransaction($object, $xaction); | ||||
} | } | ||||
return $this->applyCustomInternalTransaction($object, $xaction); | return $this->applyCustomInternalTransaction($object, $xaction); | ||||
} | } | ||||
private function applyExternalEffects( | private function applyExternalEffects( | ||||
PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | switch ($type) { | ||||
case PhabricatorTransactions::TYPE_TOKEN: | case PhabricatorTransactions::TYPE_TOKEN: | ||||
case PhabricatorTransactions::TYPE_VIEW_POLICY: | case PhabricatorTransactions::TYPE_VIEW_POLICY: | ||||
case PhabricatorTransactions::TYPE_EDIT_POLICY: | case PhabricatorTransactions::TYPE_EDIT_POLICY: | ||||
case PhabricatorTransactions::TYPE_JOIN_POLICY: | case PhabricatorTransactions::TYPE_JOIN_POLICY: | ||||
case PhabricatorTransactions::TYPE_INTERACT_POLICY: | case PhabricatorTransactions::TYPE_INTERACT_POLICY: | ||||
case PhabricatorTransactions::TYPE_INLINESTATE: | case PhabricatorTransactions::TYPE_INLINESTATE: | ||||
case PhabricatorTransactions::TYPE_SPACE: | case PhabricatorTransactions::TYPE_SPACE: | ||||
case PhabricatorTransactions::TYPE_COMMENT: | case PhabricatorTransactions::TYPE_COMMENT: | ||||
case PhabricatorTransactions::TYPE_FILE: | |||||
return $this->applyBuiltinExternalTransaction($object, $xaction); | return $this->applyBuiltinExternalTransaction($object, $xaction); | ||||
} | } | ||||
return $this->applyCustomExternalTransaction($object, $xaction); | return $this->applyCustomExternalTransaction($object, $xaction); | ||||
} | } | ||||
protected function applyCustomInternalTransaction( | protected function applyCustomInternalTransaction( | ||||
PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
▲ Show 20 Lines • Show All 108 Lines • ▼ Show 20 Lines | switch ($xaction->getTransactionType()) { | ||||
break; | break; | ||||
case PhabricatorTransactions::TYPE_VIEW_POLICY: | case PhabricatorTransactions::TYPE_VIEW_POLICY: | ||||
case PhabricatorTransactions::TYPE_SPACE: | case PhabricatorTransactions::TYPE_SPACE: | ||||
$this->scrambleFileSecrets($object); | $this->scrambleFileSecrets($object); | ||||
break; | break; | ||||
case PhabricatorTransactions::TYPE_HISTORY: | case PhabricatorTransactions::TYPE_HISTORY: | ||||
$this->sendHistory = true; | $this->sendHistory = true; | ||||
break; | break; | ||||
case PhabricatorTransactions::TYPE_FILE: | |||||
$this->applyFileTransaction($object, $xaction); | |||||
break; | |||||
} | |||||
} | |||||
private function applyFileTransaction( | |||||
PhabricatorLiskDAO $object, | |||||
PhabricatorApplicationTransaction $xaction) { | |||||
$old_map = $xaction->getOldValue(); | |||||
$new_map = $xaction->getNewValue(); | |||||
$add_phids = array(); | |||||
$rem_phids = array(); | |||||
foreach ($new_map as $phid => $mode) { | |||||
$add_phids[$phid] = $mode; | |||||
} | |||||
foreach ($old_map as $phid => $mode) { | |||||
if (!isset($new_map[$phid])) { | |||||
$rem_phids[] = $phid; | |||||
} | |||||
} | |||||
$now = PhabricatorTime::getNow(); | |||||
$object_phid = $object->getPHID(); | |||||
$attacher_phid = $this->getActingAsPHID(); | |||||
$attachment_table = new PhabricatorFileAttachment(); | |||||
$attachment_conn = $attachment_table->establishConnection('w'); | |||||
$add_sql = array(); | |||||
foreach ($add_phids as $add_phid => $add_mode) { | |||||
$add_sql[] = qsprintf( | |||||
$attachment_conn, | |||||
'(%s, %s, %s, %ns, %d, %d)', | |||||
$object_phid, | |||||
$add_phid, | |||||
$add_mode, | |||||
$attacher_phid, | |||||
$now, | |||||
$now); | |||||
} | |||||
$rem_sql = array(); | |||||
foreach ($rem_phids as $rem_phid) { | |||||
$rem_sql[] = qsprintf( | |||||
$attachment_conn, | |||||
'%s', | |||||
$rem_phid); | |||||
} | |||||
foreach (PhabricatorLiskDAO::chunkSQL($add_sql) as $chunk) { | |||||
queryfx( | |||||
$attachment_conn, | |||||
'INSERT INTO %R (objectPHID, filePHID, attachmentMode, | |||||
attacherPHID, dateCreated, dateModified) | |||||
VALUES %LQ | |||||
ON DUPLICATE KEY UPDATE | |||||
attachmentMode = VALUES(attachmentMode), | |||||
attacherPHID = VALUES(attacherPHID), | |||||
dateModified = VALUES(dateModified)', | |||||
$attachment_table, | |||||
$chunk); | |||||
} | |||||
foreach (PhabricatorLiskDAO::chunkSQL($rem_sql) as $chunk) { | |||||
queryfx( | |||||
$attachment_conn, | |||||
'DELETE FROM %R WHERE objectPHID = %s AND filePHID in (%LQ)', | |||||
$attachment_table, | |||||
$object_phid, | |||||
$chunk); | |||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Fill in a transaction's common values, like author and content source. | * Fill in a transaction's common values, like author and content source. | ||||
*/ | */ | ||||
protected function populateTransaction( | protected function populateTransaction( | ||||
PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
▲ Show 20 Lines • Show All 917 Lines • ▼ Show 20 Lines | switch ($type) { | ||||
// This is a special magic transaction which sends you history via | // This is a special magic transaction which sends you history via | ||||
// email and is only partially supported in the upstream. You don't | // email and is only partially supported in the upstream. You don't | ||||
// need any capabilities to apply it. | // need any capabilities to apply it. | ||||
return null; | return null; | ||||
case PhabricatorTransactions::TYPE_MFA: | case PhabricatorTransactions::TYPE_MFA: | ||||
// Signing a transaction group with MFA does not require permissions | // Signing a transaction group with MFA does not require permissions | ||||
// on its own. | // on its own. | ||||
return null; | return null; | ||||
case PhabricatorTransactions::TYPE_FILE: | |||||
return null; | |||||
case PhabricatorTransactions::TYPE_EDGE: | case PhabricatorTransactions::TYPE_EDGE: | ||||
return $this->getLegacyRequiredEdgeCapabilities($xaction); | return $this->getLegacyRequiredEdgeCapabilities($xaction); | ||||
default: | default: | ||||
// For other older (non-modular) transactions, always require exactly | // For other older (non-modular) transactions, always require exactly | ||||
// CAN_EDIT. Transactions which do not need CAN_EDIT or need additional | // CAN_EDIT. Transactions which do not need CAN_EDIT or need additional | ||||
// capabilities must move to ModularTransactions. | // capabilities must move to ModularTransactions. | ||||
return PhabricatorPolicyCapability::CAN_EDIT; | return PhabricatorPolicyCapability::CAN_EDIT; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 260 Lines • ▼ Show 20 Lines | $block_xactions = $this->expandRemarkupBlockTransactions( | ||||
$xactions, | $xactions, | ||||
$changes, | $changes, | ||||
$engine); | $engine); | ||||
foreach ($block_xactions as $xaction) { | foreach ($block_xactions as $xaction) { | ||||
$xactions[] = $xaction; | $xactions[] = $xaction; | ||||
} | } | ||||
$file_xaction = $this->newFileTransaction( | |||||
$object, | |||||
$xactions); | |||||
if ($file_xaction) { | |||||
$xactions[] = $file_xaction; | |||||
} | |||||
return $xactions; | return $xactions; | ||||
} | } | ||||
private function newFileTransaction( | |||||
PhabricatorLiskDAO $object, | |||||
array $xactions) { | |||||
$new_map = array(); | |||||
$file_phids = $this->extractFilePHIDs($object, $xactions); | |||||
if (!$file_phids) { | |||||
return null; | |||||
} | |||||
foreach ($file_phids as $file_phid) { | |||||
$new_map[$file_phid] = PhabricatorFileAttachment::MODE_ATTACH; | |||||
} | |||||
$xaction = $object->getApplicationTransactionTemplate() | |||||
->setTransactionType(PhabricatorTransactions::TYPE_FILE) | |||||
->setNewValue($new_map); | |||||
return $xaction; | |||||
} | |||||
private function getRemarkupChanges(array $xactions) { | private function getRemarkupChanges(array $xactions) { | ||||
$changes = array(); | $changes = array(); | ||||
foreach ($xactions as $key => $xaction) { | foreach ($xactions as $key => $xaction) { | ||||
foreach ($this->getRemarkupChangesFromTransaction($xaction) as $change) { | foreach ($this->getRemarkupChangesFromTransaction($xaction) as $change) { | ||||
$changes[] = $change; | $changes[] = $change; | ||||
} | } | ||||
} | } | ||||
▲ Show 20 Lines • Show All 582 Lines • ▼ Show 20 Lines | switch ($type) { | ||||
continue; | continue; | ||||
} | } | ||||
$errors[] = $field->validateApplicationTransactions( | $errors[] = $field->validateApplicationTransactions( | ||||
$this, | $this, | ||||
$type, | $type, | ||||
idx($groups, $field->getFieldKey(), array())); | idx($groups, $field->getFieldKey(), array())); | ||||
} | } | ||||
break; | break; | ||||
case PhabricatorTransactions::TYPE_FILE: | |||||
$errors[] = $this->validateFileTransactions( | |||||
$object, | |||||
$xactions, | |||||
$type); | |||||
break; | |||||
} | } | ||||
return array_mergev($errors); | return array_mergev($errors); | ||||
} | } | ||||
private function validateFileTransactions( | |||||
PhabricatorLiskDAO $object, | |||||
array $xactions, | |||||
$transaction_type) { | |||||
$errors = array(); | |||||
$mode_map = PhabricatorFileAttachment::getModeList(); | |||||
$mode_map = array_fuse($mode_map); | |||||
$file_phids = array(); | |||||
foreach ($xactions as $xaction) { | |||||
$new = $xaction->getNewValue(); | |||||
if (!is_array($new)) { | |||||
$errors[] = new PhabricatorApplicationTransactionValidationError( | |||||
$transaction_type, | |||||
pht('Invalid'), | |||||
pht( | |||||
'File attachment transaction must have a map of files to '. | |||||
'attachment modes, found "%s".', | |||||
phutil_describe_type($new)), | |||||
$xaction); | |||||
continue; | |||||
} | |||||
foreach ($new as $file_phid => $attachment_mode) { | |||||
$file_phids[$file_phid] = $file_phid; | |||||
if (is_string($attachment_mode) && isset($mode_map[$attachment_mode])) { | |||||
continue; | |||||
} | |||||
if (!is_string($attachment_mode)) { | |||||
$errors[] = new PhabricatorApplicationTransactionValidationError( | |||||
$transaction_type, | |||||
pht('Invalid'), | |||||
pht( | |||||
'File attachment mode (for file "%s") is invalid. Expected '. | |||||
'a string, found "%s".', | |||||
$file_phid, | |||||
phutil_describe_type($attachment_mode)), | |||||
$xaction); | |||||
} else { | |||||
$errors[] = new PhabricatorApplicationTransactionValidationError( | |||||
$transaction_type, | |||||
pht('Invalid'), | |||||
pht( | |||||
'File attachment mode "%s" (for file "%s") is invalid. Valid '. | |||||
'modes are: %s.', | |||||
$attachment_mode, | |||||
$file_phid, | |||||
pht_list($mode_map)), | |||||
$xaction); | |||||
} | |||||
} | |||||
} | |||||
if ($file_phids) { | |||||
$file_map = id(new PhabricatorFileQuery()) | |||||
->setViewer($this->getActor()) | |||||
->withPHIDs($file_phids) | |||||
->execute(); | |||||
$file_map = mpull($file_map, null, 'getPHID'); | |||||
} else { | |||||
$file_map = array(); | |||||
} | |||||
foreach ($xactions as $xaction) { | |||||
$new = $xaction->getNewValue(); | |||||
if (!is_array($new)) { | |||||
continue; | |||||
} | |||||
foreach ($new as $file_phid => $attachment_mode) { | |||||
if (isset($file_map[$file_phid])) { | |||||
continue; | |||||
} | |||||
$errors[] = new PhabricatorApplicationTransactionValidationError( | |||||
$transaction_type, | |||||
pht('Invalid'), | |||||
pht( | |||||
'File "%s" is invalid: it could not be loaded, or you do not '. | |||||
'have permission to view it. You must be able to see a file to '. | |||||
'attach it to an object.', | |||||
$file_phid), | |||||
$xaction); | |||||
} | |||||
} | |||||
return $errors; | |||||
} | |||||
public function validatePolicyTransaction( | public function validatePolicyTransaction( | ||||
PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
array $xactions, | array $xactions, | ||||
$transaction_type, | $transaction_type, | ||||
$capability) { | $capability) { | ||||
$actor = $this->requireActor(); | $actor = $this->requireActor(); | ||||
$errors = array(); | $errors = array(); | ||||
▲ Show 20 Lines • Show All 2,739 Lines • Show Last 20 Lines |
This comment has a TODO.