2014-02-19 01:32:55 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class DifferentialTransactionEditor
|
|
|
|
extends PhabricatorApplicationTransactionEditor {
|
|
|
|
|
|
|
|
public function getTransactionTypes() {
|
|
|
|
$types = parent::getTransactionTypes();
|
|
|
|
|
2014-02-25 00:57:26 +01:00
|
|
|
$types[] = PhabricatorTransactions::TYPE_COMMENT;
|
2014-02-21 20:54:32 +01:00
|
|
|
$types[] = PhabricatorTransactions::TYPE_EDGE;
|
2014-02-19 01:32:55 +01:00
|
|
|
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
|
|
|
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
|
|
|
|
2014-02-25 00:57:26 +01:00
|
|
|
$types[] = DifferentialTransaction::TYPE_ACTION;
|
2014-02-25 00:57:35 +01:00
|
|
|
$types[] = DifferentialTransaction::TYPE_INLINE;
|
2014-02-25 00:57:26 +01:00
|
|
|
|
2014-02-21 20:53:48 +01:00
|
|
|
/*
|
|
|
|
|
2014-02-19 01:32:55 +01:00
|
|
|
$types[] = DifferentialTransaction::TYPE_UPDATE;
|
|
|
|
*/
|
|
|
|
|
|
|
|
return $types;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getCustomTransactionOldValue(
|
|
|
|
PhabricatorLiskDAO $object,
|
|
|
|
PhabricatorApplicationTransaction $xaction) {
|
|
|
|
|
|
|
|
switch ($xaction->getTransactionType()) {
|
2014-02-21 20:53:48 +01:00
|
|
|
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
|
|
|
return $object->getViewPolicy();
|
|
|
|
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
|
|
|
return $object->getEditPolicy();
|
2014-02-25 00:57:26 +01:00
|
|
|
case DifferentialTransaction::TYPE_ACTION:
|
|
|
|
return null;
|
2014-02-25 00:57:35 +01:00
|
|
|
case DifferentialTransaction::TYPE_INLINE:
|
|
|
|
return null;
|
2014-02-19 01:32:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return parent::getCustomTransactionOldValue($object, $xaction);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getCustomTransactionNewValue(
|
|
|
|
PhabricatorLiskDAO $object,
|
|
|
|
PhabricatorApplicationTransaction $xaction) {
|
|
|
|
|
|
|
|
switch ($xaction->getTransactionType()) {
|
2014-02-21 20:53:48 +01:00
|
|
|
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
|
|
|
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
2014-02-25 00:57:26 +01:00
|
|
|
case DifferentialTransaction::TYPE_ACTION:
|
2014-02-21 20:53:48 +01:00
|
|
|
return $xaction->getNewValue();
|
2014-02-25 00:57:35 +01:00
|
|
|
case DifferentialTransaction::TYPE_INLINE:
|
|
|
|
return null;
|
2014-02-19 01:32:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return parent::getCustomTransactionNewValue($object, $xaction);
|
|
|
|
}
|
|
|
|
|
2014-02-25 00:57:26 +01:00
|
|
|
protected function transactionHasEffect(
|
|
|
|
PhabricatorLiskDAO $object,
|
|
|
|
PhabricatorApplicationTransaction $xaction) {
|
|
|
|
|
|
|
|
switch ($xaction->getTransactionType()) {
|
2014-02-25 00:57:35 +01:00
|
|
|
case DifferentialTransaction::TYPE_INLINE:
|
|
|
|
return $xaction->hasComment();
|
2014-02-25 00:57:49 +01:00
|
|
|
case DifferentialTransaction::TYPE_ACTION:
|
|
|
|
$status_closed = ArcanistDifferentialRevisionStatus::CLOSED;
|
|
|
|
$status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED;
|
|
|
|
$status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
|
|
|
|
$status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION;
|
|
|
|
|
|
|
|
switch ($xaction->getNewValue()) {
|
|
|
|
case DifferentialAction::ACTION_CLOSE:
|
|
|
|
return ($object->getStatus() != $status_closed);
|
|
|
|
case DifferentialAction::ACTION_ABANDON:
|
|
|
|
return ($object->getStatus() != $status_abandoned);
|
|
|
|
case DifferentialAction::ACTION_RECLAIM:
|
|
|
|
return ($object->getStatus() == $status_abandoned);
|
|
|
|
case DifferentialAction::ACTION_REOPEN:
|
|
|
|
return ($object->getStatus() == $status_closed);
|
|
|
|
case DifferentialAction::ACTION_RETHINK:
|
|
|
|
return ($object->getStatus() != $status_revision);
|
|
|
|
case DifferentialAction::ACTION_REQUEST:
|
|
|
|
return ($object->getStatus() != $status_review);
|
|
|
|
}
|
2014-02-25 00:57:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return parent::transactionHasEffect($object, $xaction);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-02-19 01:32:55 +01:00
|
|
|
protected function applyCustomInternalTransaction(
|
|
|
|
PhabricatorLiskDAO $object,
|
|
|
|
PhabricatorApplicationTransaction $xaction) {
|
|
|
|
|
|
|
|
switch ($xaction->getTransactionType()) {
|
2014-02-21 20:53:48 +01:00
|
|
|
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
|
|
|
$object->setViewPolicy($xaction->getNewValue());
|
|
|
|
return;
|
|
|
|
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
|
|
|
$object->setEditPolicy($xaction->getNewValue());
|
|
|
|
return;
|
2014-02-21 20:54:08 +01:00
|
|
|
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
|
2014-02-25 00:57:26 +01:00
|
|
|
case PhabricatorTransactions::TYPE_COMMENT:
|
2014-02-25 00:57:35 +01:00
|
|
|
case DifferentialTransaction::TYPE_INLINE:
|
|
|
|
return;
|
2014-02-21 20:54:32 +01:00
|
|
|
case PhabricatorTransactions::TYPE_EDGE:
|
|
|
|
// TODO: When removing reviewers, we may be able to move the revision
|
|
|
|
// to "Accepted".
|
2014-02-21 20:54:08 +01:00
|
|
|
return;
|
2014-02-25 00:57:26 +01:00
|
|
|
case DifferentialTransaction::TYPE_ACTION:
|
2014-02-25 00:57:49 +01:00
|
|
|
$status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
|
|
|
|
$status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION;
|
|
|
|
|
|
|
|
switch ($xaction->getNewValue()) {
|
|
|
|
case DifferentialAction::ACTION_ABANDON:
|
|
|
|
$object->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED);
|
|
|
|
break;
|
|
|
|
case DifferentialAction::ACTION_RETHINK:
|
|
|
|
$object->setStatus($status_revision);
|
|
|
|
break;
|
|
|
|
case DifferentialAction::ACTION_RECLAIM:
|
|
|
|
$object->setStatus($status_review);
|
|
|
|
// TODO: Update review status?
|
|
|
|
break;
|
|
|
|
case DifferentialAction::ACTION_REOPEN:
|
|
|
|
$object->setStatus($status_review);
|
|
|
|
// TODO: Update review status?
|
|
|
|
break;
|
|
|
|
case DifferentialAction::ACTION_REQUEST:
|
|
|
|
$object->setStatus($status_review);
|
|
|
|
// TODO: Update review status?
|
|
|
|
break;
|
|
|
|
case DifferentialAction::ACTION_CLOSE:
|
|
|
|
if (!$object->getDateCommitted()) {
|
|
|
|
// TODO: Can we remove this? It is probably no longer used by
|
|
|
|
// anything anymore. See also T4434.
|
|
|
|
$object->setDateCommitted(time());
|
|
|
|
}
|
|
|
|
$object->setStatus(ArcanistDifferentialRevisionStatus::CLOSED);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// TODO: For now, we're just shipping the rest of these through
|
|
|
|
// without acting on them.
|
|
|
|
break;
|
|
|
|
}
|
2014-02-25 00:57:26 +01:00
|
|
|
return null;
|
2014-02-19 01:32:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return parent::applyCustomInternalTransaction($object, $xaction);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function applyCustomExternalTransaction(
|
|
|
|
PhabricatorLiskDAO $object,
|
|
|
|
PhabricatorApplicationTransaction $xaction) {
|
|
|
|
|
|
|
|
switch ($xaction->getTransactionType()) {
|
2014-02-21 20:53:48 +01:00
|
|
|
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
|
|
|
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
|
|
|
return;
|
2014-02-21 20:54:08 +01:00
|
|
|
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
|
2014-02-21 20:54:32 +01:00
|
|
|
case PhabricatorTransactions::TYPE_EDGE:
|
2014-02-25 00:57:26 +01:00
|
|
|
case PhabricatorTransactions::TYPE_COMMENT:
|
|
|
|
case DifferentialTransaction::TYPE_ACTION:
|
2014-02-25 00:57:35 +01:00
|
|
|
case DifferentialTransaction::TYPE_INLINE:
|
2014-02-21 20:54:08 +01:00
|
|
|
return;
|
2014-02-19 01:32:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return parent::applyCustomExternalTransaction($object, $xaction);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function validateTransaction(
|
|
|
|
PhabricatorLiskDAO $object,
|
|
|
|
$type,
|
|
|
|
array $xactions) {
|
|
|
|
|
|
|
|
$errors = parent::validateTransaction($object, $type, $xactions);
|
|
|
|
|
2014-02-25 00:57:49 +01:00
|
|
|
foreach ($xactions as $xaction) {
|
|
|
|
switch ($type) {
|
|
|
|
case DifferentialTransaction::TYPE_ACTION:
|
|
|
|
$error = $this->validateDifferentialAction(
|
|
|
|
$object,
|
|
|
|
$type,
|
|
|
|
$xaction,
|
|
|
|
$xaction->getNewValue());
|
|
|
|
if ($error) {
|
|
|
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
|
|
|
$type,
|
|
|
|
pht('Invalid'),
|
|
|
|
$error,
|
|
|
|
$xaction);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-02-19 01:32:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return $errors;
|
|
|
|
}
|
|
|
|
|
2014-02-25 00:57:49 +01:00
|
|
|
private function validateDifferentialAction(
|
|
|
|
DifferentialRevision $revision,
|
|
|
|
$type,
|
|
|
|
DifferentialTransaction $xaction,
|
|
|
|
$action) {
|
|
|
|
|
|
|
|
$author_phid = $revision->getAuthorPHID();
|
|
|
|
$actor_phid = $this->getActor()->getPHID();
|
|
|
|
$actor_is_author = ($author_phid == $actor_phid);
|
|
|
|
|
|
|
|
$config_close_key = 'differential.always-allow-close';
|
|
|
|
$always_allow_close = PhabricatorEnv::getEnvConfig($config_close_key);
|
|
|
|
|
|
|
|
$config_reopen_key = 'differential.allow-reopen';
|
|
|
|
$allow_reopen = PhabricatorEnv::getEnvConfig($config_reopen_key);
|
|
|
|
|
|
|
|
$revision_status = $revision->getStatus();
|
|
|
|
|
|
|
|
$status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED;
|
|
|
|
$status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED;
|
|
|
|
$status_closed = ArcanistDifferentialRevisionStatus::CLOSED;
|
|
|
|
|
|
|
|
switch ($action) {
|
|
|
|
case DifferentialAction::ACTION_ABANDON:
|
|
|
|
if (!$actor_is_author) {
|
|
|
|
return pht(
|
|
|
|
"You can not abandon this revision because you do not own it. ".
|
|
|
|
"You can only abandon revisions you own.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($revision_status == $status_closed) {
|
|
|
|
return pht(
|
|
|
|
"You can not abandon this revision because it has already been ".
|
|
|
|
"closed.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: Abandons of already-abandoned revisions are treated as no-op
|
|
|
|
// instead of invalid. Other abandons are OK.
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DifferentialAction::ACTION_RECLAIM:
|
|
|
|
if (!$actor_is_author) {
|
|
|
|
return pht(
|
|
|
|
"You can not reclaim this revision because you do not own ".
|
|
|
|
"it. You can only reclaim revisions you own.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($revision_status == $status_closed) {
|
|
|
|
return pht(
|
|
|
|
"You can not reclaim this revision because it has already been ".
|
|
|
|
"closed.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: Reclaims of other non-abandoned revisions are treated as no-op
|
|
|
|
// instead of invalid.
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DifferentialAction::ACTION_REOPEN:
|
|
|
|
if (!$allow_reopen) {
|
|
|
|
return pht(
|
|
|
|
'The reopen action is not enabled on this Phabricator install. '.
|
|
|
|
'Adjust your configuration to enable it.');
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: If the revision is not closed, this is caught as a no-op
|
|
|
|
// instead of an invalid transaction.
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DifferentialAction::ACTION_RETHINK:
|
|
|
|
if (!$actor_is_author) {
|
|
|
|
return pht(
|
|
|
|
"You can not plan changes to this revision because you do not ".
|
|
|
|
"own it. To plan chagnes to a revision, you must be its owner.");
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ($revision_status) {
|
|
|
|
case ArcanistDifferentialRevisionStatus::ACCEPTED:
|
|
|
|
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
|
|
|
|
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
|
|
|
|
// These are OK.
|
|
|
|
break;
|
|
|
|
case ArcanistDifferentialRevisionStatus::ABANDONED:
|
|
|
|
return pht(
|
|
|
|
"You can not plan changes to this revision because it has ".
|
|
|
|
"been abandoned.");
|
|
|
|
case ArcanistDifferentialRevisionStatus::CLOSED:
|
|
|
|
return pht(
|
|
|
|
"You can not plan changes to this revision because it has ".
|
|
|
|
"already been closed.");
|
|
|
|
default:
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Encountered unexpected revision status ("%s") when '.
|
|
|
|
'validating "%s" action.',
|
|
|
|
$revision_status,
|
|
|
|
$action));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DifferentialAction::ACTION_REQUEST:
|
|
|
|
if (!$actor_is_author) {
|
|
|
|
return pht(
|
|
|
|
"You can not request review of this revision because you do ".
|
|
|
|
"not own it. To request review of a revision, you must be its ".
|
|
|
|
"owner.");
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ($revision_status) {
|
|
|
|
case ArcanistDifferentialRevisionStatus::ACCEPTED:
|
|
|
|
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
|
|
|
|
// These are OK.
|
|
|
|
break;
|
|
|
|
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
|
|
|
|
// This will be caught as "no effect" later on.
|
|
|
|
break;
|
|
|
|
case ArcanistDifferentialRevisionStatus::ABANDONED:
|
|
|
|
return pht(
|
|
|
|
"You can not request review of this revision because it has ".
|
|
|
|
"been abandoned. Instead, reclaim it.");
|
|
|
|
case ArcanistDifferentialRevisionStatus::CLOSED:
|
|
|
|
return pht(
|
|
|
|
"You can not request review of this revision because it has ".
|
|
|
|
"already been closed.");
|
|
|
|
default:
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Encountered unexpected revision status ("%s") when '.
|
|
|
|
'validating "%s" action.',
|
|
|
|
$revision_status,
|
|
|
|
$action));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DifferentialAction::ACTION_CLOSE:
|
|
|
|
|
|
|
|
// TODO: Permit the daemons to take this action in all cases.
|
|
|
|
|
|
|
|
if (!$actor_is_author && !$always_allow_close) {
|
|
|
|
return pht(
|
|
|
|
"You can not close this revision because you do not own it. To ".
|
|
|
|
"close a revision, you must be its owner.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($revision_status != $status_accepted) {
|
|
|
|
return pht(
|
|
|
|
"You can not close this revision because it has not been ".
|
|
|
|
"accepted. You can only close accepted revisions.");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2014-02-25 00:57:35 +01:00
|
|
|
protected function sortTransactions(array $xactions) {
|
|
|
|
$head = array();
|
|
|
|
$tail = array();
|
|
|
|
|
|
|
|
// Move bare comments to the end, so the actions precede them.
|
|
|
|
foreach ($xactions as $xaction) {
|
|
|
|
$type = $xaction->getTransactionType();
|
|
|
|
if ($type == DifferentialTransaction::TYPE_INLINE) {
|
|
|
|
$tail[] = $xaction;
|
|
|
|
} else {
|
|
|
|
$head[] = $xaction;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_values(array_merge($head, $tail));
|
|
|
|
}
|
|
|
|
|
2014-02-19 01:32:55 +01:00
|
|
|
protected function requireCapabilities(
|
|
|
|
PhabricatorLiskDAO $object,
|
|
|
|
PhabricatorApplicationTransaction $xaction) {
|
|
|
|
|
|
|
|
switch ($xaction->getTransactionType()) {
|
|
|
|
}
|
|
|
|
|
|
|
|
return parent::requireCapabilities($object, $xaction);
|
|
|
|
}
|
|
|
|
|
2014-02-25 00:57:16 +01:00
|
|
|
protected function shouldSendMail(
|
|
|
|
PhabricatorLiskDAO $object,
|
|
|
|
array $xactions) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getMailTo(PhabricatorLiskDAO $object) {
|
|
|
|
$phids = array();
|
|
|
|
$phids[] = $object->getAuthorPHID();
|
|
|
|
foreach ($object->getReviewerStatus() as $reviewer) {
|
|
|
|
$phids[] = $reviewer->getReviewerPHID();
|
|
|
|
}
|
|
|
|
return $phids;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getMailSubjectPrefix() {
|
|
|
|
return PhabricatorEnv::getEnvConfig('metamta.differential.subject-prefix');
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
|
|
|
|
return id(new DifferentialReplyHandler())
|
|
|
|
->setMailReceiver($object);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
|
|
|
|
$id = $object->getID();
|
|
|
|
$title = $object->getTitle();
|
|
|
|
|
|
|
|
$original_title = $object->getOriginalTitle();
|
|
|
|
|
|
|
|
$subject = "D{$id}: {$title}";
|
|
|
|
$thread_topic = "D{$id}: {$original_title}";
|
|
|
|
|
|
|
|
return id(new PhabricatorMetaMTAMail())
|
|
|
|
->setSubject($subject)
|
|
|
|
->addHeader('Thread-Topic', $thread_topic);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function buildMailBody(
|
|
|
|
PhabricatorLiskDAO $object,
|
|
|
|
array $xactions) {
|
|
|
|
|
|
|
|
$body = parent::buildMailBody($object, $xactions);
|
|
|
|
|
|
|
|
$body->addTextSection(
|
|
|
|
pht('REVISION DETAIL'),
|
|
|
|
PhabricatorEnv::getProductionURI('/D'.$object->getID()));
|
|
|
|
|
|
|
|
return $body;
|
|
|
|
}
|
|
|
|
|
2014-02-19 01:32:55 +01:00
|
|
|
protected function supportsSearch() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function extractFilePHIDsFromCustomTransaction(
|
|
|
|
PhabricatorLiskDAO $object,
|
|
|
|
PhabricatorApplicationTransaction $xaction) {
|
|
|
|
|
|
|
|
switch ($xaction->getTransactionType()) {
|
|
|
|
}
|
|
|
|
|
|
|
|
return parent::extractFilePHIDsFromCustomTransaction($object, $xaction);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|