diff --git a/src/applications/transactions/constants/PhabricatorTransactions.php b/src/applications/transactions/constants/PhabricatorTransactions.php --- a/src/applications/transactions/constants/PhabricatorTransactions.php +++ b/src/applications/transactions/constants/PhabricatorTransactions.php @@ -15,6 +15,7 @@ const TYPE_CREATE = 'core:create'; const TYPE_COLUMNS = 'core:columns'; const TYPE_SUBTYPE = 'core:subtype'; + const TYPE_HISTORY = 'core:history'; const COLOR_RED = 'red'; const COLOR_ORANGE = 'orange'; diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -83,6 +83,7 @@ private $webhookMap = array(); private $transactionQueue = array(); + private $sendHistory = false; const STORAGE_ENCODING_BINARY = 'binary'; @@ -300,6 +301,7 @@ $types = array(); $types[] = PhabricatorTransactions::TYPE_CREATE; + $types[] = PhabricatorTransactions::TYPE_HISTORY; if ($this->object instanceof PhabricatorEditEngineSubtypeInterface) { $types[] = PhabricatorTransactions::TYPE_SUBTYPE; @@ -377,6 +379,7 @@ switch ($type) { case PhabricatorTransactions::TYPE_CREATE: + case PhabricatorTransactions::TYPE_HISTORY: return null; case PhabricatorTransactions::TYPE_SUBTYPE: return $object->getEditEngineSubtype(); @@ -468,6 +471,7 @@ case PhabricatorTransactions::TYPE_TOKEN: case PhabricatorTransactions::TYPE_INLINESTATE: case PhabricatorTransactions::TYPE_SUBTYPE: + case PhabricatorTransactions::TYPE_HISTORY: return $xaction->getNewValue(); case PhabricatorTransactions::TYPE_SPACE: $space_phid = $xaction->getNewValue(); @@ -520,6 +524,7 @@ switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_CREATE: + case PhabricatorTransactions::TYPE_HISTORY: return true; case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getCustomFieldForTransaction($object, $xaction); @@ -604,6 +609,7 @@ $field = $this->getCustomFieldForTransaction($object, $xaction); return $field->applyApplicationTransactionInternalEffects($xaction); case PhabricatorTransactions::TYPE_CREATE: + case PhabricatorTransactions::TYPE_HISTORY: case PhabricatorTransactions::TYPE_SUBTYPE: case PhabricatorTransactions::TYPE_TOKEN: case PhabricatorTransactions::TYPE_VIEW_POLICY: @@ -665,6 +671,7 @@ $field = $this->getCustomFieldForTransaction($object, $xaction); return $field->applyApplicationTransactionExternalEffects($xaction); case PhabricatorTransactions::TYPE_CREATE: + case PhabricatorTransactions::TYPE_HISTORY: case PhabricatorTransactions::TYPE_SUBTYPE: case PhabricatorTransactions::TYPE_EDGE: case PhabricatorTransactions::TYPE_TOKEN: @@ -800,6 +807,9 @@ case PhabricatorTransactions::TYPE_SPACE: $this->scrambleFileSecrets($object); break; + case PhabricatorTransactions::TYPE_HISTORY: + $this->sendHistory = true; + break; } } @@ -1317,6 +1327,13 @@ $this->publishFeedStory($object, $xactions, $mailed); } + if ($this->sendHistory) { + $history_mail = $this->buildHistoryMail($object); + if ($history_mail) { + $messages[] = $history_mail; + } + } + // NOTE: This actually sends the mail. We do this last to reduce the chance // that we send some mail, hit an exception, then send the mail again when // retrying. @@ -2557,6 +2574,25 @@ $unexpandable = array(); } + $messages = $this->buildMailWithRecipients( + $object, + $xactions, + $email_to, + $email_cc, + $unexpandable); + + $this->runHeraldMailRules($messages); + + return $messages; + } + + private function buildMailWithRecipients( + PhabricatorLiskDAO $object, + array $xactions, + array $email_to, + array $email_cc, + array $unexpandable) { + $targets = $this->buildReplyHandler($object) ->setUnexpandablePHIDs($unexpandable) ->getMailTargets($email_to, $email_cc); @@ -2603,8 +2639,6 @@ } } - $this->runHeraldMailRules($messages); - return $messages; } @@ -3671,6 +3705,7 @@ 'mailMutedPHIDs', 'webhookMap', 'silent', + 'sendHistory', ); } @@ -4328,4 +4363,32 @@ return true; } + private function buildHistoryMail(PhabricatorLiskDAO $object) { + $viewer = $this->requireActor(); + $recipient_phid = $this->getActingAsPHID(); + + // Load every transaction so we can build a mail message with a complete + // history for the object. + $query = PhabricatorApplicationTransactionQuery::newQueryForObject($object); + $xactions = $query + ->setViewer($viewer) + ->withObjectPHIDs(array($object->getPHID())) + ->execute(); + $xactions = array_reverse($xactions); + + $mail_messages = $this->buildMailWithRecipients( + $object, + $xactions, + array($recipient_phid), + array(), + array()); + $mail = head($mail_messages); + + // Since the user explicitly requested "!history", force delivery of this + // message regardless of their other mail settings. + $mail->setForceDelivery(true); + + return $mail; + } + } diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -545,11 +545,18 @@ return false; } + $xaction_type = $this->getTransactionType(); + + // Always hide requests for object history. + if ($xaction_type === PhabricatorTransactions::TYPE_HISTORY) { + return true; + } + // Hide creation transactions if the old value is empty. These are - // transactions like "alice set the task tile to: ...", which are + // transactions like "alice set the task title to: ...", which are // essentially never interesting. if ($this->getIsCreateTransaction()) { - switch ($this->getTransactionType()) { + switch ($xaction_type) { case PhabricatorTransactions::TYPE_CREATE: case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: