Path: blob/master/src/applications/differential/storage/DifferentialTransaction.php
12256 views
<?php12final class DifferentialTransaction3extends PhabricatorModularTransaction {45private $isCommandeerSideEffect;67const TYPE_INLINE = 'differential:inline';8const TYPE_ACTION = 'differential:action';910const MAILTAG_REVIEWERS = 'differential-reviewers';11const MAILTAG_CLOSED = 'differential-committed';12const MAILTAG_CC = 'differential-cc';13const MAILTAG_COMMENT = 'differential-comment';14const MAILTAG_UPDATED = 'differential-updated';15const MAILTAG_REVIEW_REQUEST = 'differential-review-request';16const MAILTAG_OTHER = 'differential-other';1718public function getBaseTransactionClass() {19return 'DifferentialRevisionTransactionType';20}2122protected function newFallbackModularTransactionType() {23// TODO: This allows us to render modern strings for older transactions24// without doing a migration. At some point, we should do a migration and25// throw this away.2627// NOTE: Old reviewer edits are raw edge transactions. They could be28// migrated to modular transactions when the rest of this migrates.2930$xaction_type = $this->getTransactionType();31if ($xaction_type == PhabricatorTransactions::TYPE_CUSTOMFIELD) {32switch ($this->getMetadataValue('customfield:key')) {33case 'differential:title':34return new DifferentialRevisionTitleTransaction();35case 'differential:test-plan':36return new DifferentialRevisionTestPlanTransaction();37case 'differential:repository':38return new DifferentialRevisionRepositoryTransaction();39}40}4142return parent::newFallbackModularTransactionType();43}444546public function setIsCommandeerSideEffect($is_side_effect) {47$this->isCommandeerSideEffect = $is_side_effect;48return $this;49}5051public function getIsCommandeerSideEffect() {52return $this->isCommandeerSideEffect;53}5455public function getApplicationName() {56return 'differential';57}5859public function getApplicationTransactionType() {60return DifferentialRevisionPHIDType::TYPECONST;61}6263public function getApplicationTransactionCommentObject() {64return new DifferentialTransactionComment();65}6667public function shouldHide() {68$old = $this->getOldValue();69$new = $this->getNewValue();7071switch ($this->getTransactionType()) {72case DifferentialRevisionRequestReviewTransaction::TRANSACTIONTYPE:73// Don't hide the initial "X requested review: ..." transaction from74// mail or feed even when it occurs during creation. We need this75// transaction to survive so we'll generate mail and feed stories when76// revisions immediately leave the draft state. See T13035 for77// discussion.78return false;79}8081return parent::shouldHide();82}8384public function shouldHideForMail(array $xactions) {85switch ($this->getTransactionType()) {86case DifferentialRevisionReviewersTransaction::TRANSACTIONTYPE:87// Don't hide the initial "X added reviewers: ..." transaction during88// object creation from mail. See T12118 and PHI54.89return false;90}9192return parent::shouldHideForMail($xactions);93}949596public function isInlineCommentTransaction() {97switch ($this->getTransactionType()) {98case self::TYPE_INLINE:99return true;100}101102return parent::isInlineCommentTransaction();103}104105public function getRequiredHandlePHIDs() {106$phids = parent::getRequiredHandlePHIDs();107108$old = $this->getOldValue();109$new = $this->getNewValue();110111switch ($this->getTransactionType()) {112case self::TYPE_ACTION:113if ($new == DifferentialAction::ACTION_CLOSE &&114$this->getMetadataValue('isCommitClose')) {115$phids[] = $this->getMetadataValue('commitPHID');116if ($this->getMetadataValue('committerPHID')) {117$phids[] = $this->getMetadataValue('committerPHID');118}119if ($this->getMetadataValue('authorPHID')) {120$phids[] = $this->getMetadataValue('authorPHID');121}122}123break;124}125126return $phids;127}128129public function getActionStrength() {130switch ($this->getTransactionType()) {131case self::TYPE_ACTION:132return 300;133}134135return parent::getActionStrength();136}137138139public function getActionName() {140switch ($this->getTransactionType()) {141case self::TYPE_INLINE:142return pht('Commented On');143case self::TYPE_ACTION:144$map = array(145DifferentialAction::ACTION_ACCEPT => pht('Accepted'),146DifferentialAction::ACTION_REJECT => pht('Requested Changes To'),147DifferentialAction::ACTION_RETHINK => pht('Planned Changes To'),148DifferentialAction::ACTION_ABANDON => pht('Abandoned'),149DifferentialAction::ACTION_CLOSE => pht('Closed'),150DifferentialAction::ACTION_REQUEST => pht('Requested A Review Of'),151DifferentialAction::ACTION_RESIGN => pht('Resigned From'),152DifferentialAction::ACTION_ADDREVIEWERS => pht('Added Reviewers'),153DifferentialAction::ACTION_CLAIM => pht('Commandeered'),154DifferentialAction::ACTION_REOPEN => pht('Reopened'),155);156$name = idx($map, $this->getNewValue());157if ($name !== null) {158return $name;159}160break;161}162163return parent::getActionName();164}165166public function getMailTags() {167$tags = array();168169switch ($this->getTransactionType()) {170case PhabricatorTransactions::TYPE_SUBSCRIBERS;171$tags[] = self::MAILTAG_CC;172break;173case self::TYPE_ACTION:174switch ($this->getNewValue()) {175case DifferentialAction::ACTION_CLOSE:176$tags[] = self::MAILTAG_CLOSED;177break;178}179break;180case DifferentialRevisionUpdateTransaction::TRANSACTIONTYPE:181$old = $this->getOldValue();182if ($old === null) {183$tags[] = self::MAILTAG_REVIEW_REQUEST;184} else {185$tags[] = self::MAILTAG_UPDATED;186}187break;188case PhabricatorTransactions::TYPE_COMMENT:189case self::TYPE_INLINE:190$tags[] = self::MAILTAG_COMMENT;191break;192case DifferentialRevisionReviewersTransaction::TRANSACTIONTYPE:193$tags[] = self::MAILTAG_REVIEWERS;194break;195case DifferentialRevisionCloseTransaction::TRANSACTIONTYPE:196$tags[] = self::MAILTAG_CLOSED;197break;198}199200if (!$tags) {201$tags[] = self::MAILTAG_OTHER;202}203204return $tags;205}206207public function getTitle() {208$author_phid = $this->getAuthorPHID();209$author_handle = $this->renderHandleLink($author_phid);210211$old = $this->getOldValue();212$new = $this->getNewValue();213214switch ($this->getTransactionType()) {215case self::TYPE_INLINE:216return pht(217'%s added inline comments.',218$author_handle);219case self::TYPE_ACTION:220switch ($new) {221case DifferentialAction::ACTION_CLOSE:222if (!$this->getMetadataValue('isCommitClose')) {223return DifferentialAction::getBasicStoryText(224$new,225$author_handle);226}227$commit_name = $this->renderHandleLink(228$this->getMetadataValue('commitPHID'));229$committer_phid = $this->getMetadataValue('committerPHID');230$author_phid = $this->getMetadataValue('authorPHID');231if ($this->getHandleIfExists($committer_phid)) {232$committer_name = $this->renderHandleLink($committer_phid);233} else {234$committer_name = $this->getMetadataValue('committerName');235}236if ($this->getHandleIfExists($author_phid)) {237$author_name = $this->renderHandleLink($author_phid);238} else {239$author_name = $this->getMetadataValue('authorName');240}241242if ($committer_name && ($committer_name != $author_name)) {243return pht(244'Closed by commit %s (authored by %s, committed by %s).',245$commit_name,246$author_name,247$committer_name);248} else {249return pht(250'Closed by commit %s (authored by %s).',251$commit_name,252$author_name);253}254break;255default:256return DifferentialAction::getBasicStoryText($new, $author_handle);257}258break;259}260261return parent::getTitle();262}263264public function renderExtraInformationLink() {265if ($this->getMetadataValue('revisionMatchData')) {266$details_href =267'/differential/revision/closedetails/'.$this->getPHID().'/';268$details_link = javelin_tag(269'a',270array(271'href' => $details_href,272'sigil' => 'workflow',273),274pht('Explain Why'));275return $details_link;276}277return parent::renderExtraInformationLink();278}279280public function getTitleForFeed() {281$author_phid = $this->getAuthorPHID();282$object_phid = $this->getObjectPHID();283284$old = $this->getOldValue();285$new = $this->getNewValue();286287$author_link = $this->renderHandleLink($author_phid);288$object_link = $this->renderHandleLink($object_phid);289290switch ($this->getTransactionType()) {291case self::TYPE_INLINE:292return pht(293'%s added inline comments to %s.',294$author_link,295$object_link);296case self::TYPE_ACTION:297switch ($new) {298case DifferentialAction::ACTION_ACCEPT:299return pht(300'%s accepted %s.',301$author_link,302$object_link);303case DifferentialAction::ACTION_REJECT:304return pht(305'%s requested changes to %s.',306$author_link,307$object_link);308case DifferentialAction::ACTION_RETHINK:309return pht(310'%s planned changes to %s.',311$author_link,312$object_link);313case DifferentialAction::ACTION_ABANDON:314return pht(315'%s abandoned %s.',316$author_link,317$object_link);318case DifferentialAction::ACTION_CLOSE:319if (!$this->getMetadataValue('isCommitClose')) {320return pht(321'%s closed %s.',322$author_link,323$object_link);324} else {325$commit_name = $this->renderHandleLink(326$this->getMetadataValue('commitPHID'));327$committer_phid = $this->getMetadataValue('committerPHID');328$author_phid = $this->getMetadataValue('authorPHID');329330if ($this->getHandleIfExists($committer_phid)) {331$committer_name = $this->renderHandleLink($committer_phid);332} else {333$committer_name = $this->getMetadataValue('committerName');334}335336if ($this->getHandleIfExists($author_phid)) {337$author_name = $this->renderHandleLink($author_phid);338} else {339$author_name = $this->getMetadataValue('authorName');340}341342// Check if the committer and author are the same. They're the343// same if both resolved and are the same user, or if neither344// resolved and the text is identical.345if ($committer_phid && $author_phid) {346$same_author = ($committer_phid == $author_phid);347} else if (!$committer_phid && !$author_phid) {348$same_author = ($committer_name == $author_name);349} else {350$same_author = false;351}352353if ($committer_name && !$same_author) {354return pht(355'%s closed %s by committing %s (authored by %s).',356$author_link,357$object_link,358$commit_name,359$author_name);360} else {361return pht(362'%s closed %s by committing %s.',363$author_link,364$object_link,365$commit_name);366}367}368break;369370case DifferentialAction::ACTION_REQUEST:371return pht(372'%s requested review of %s.',373$author_link,374$object_link);375case DifferentialAction::ACTION_RECLAIM:376return pht(377'%s reclaimed %s.',378$author_link,379$object_link);380case DifferentialAction::ACTION_RESIGN:381return pht(382'%s resigned from %s.',383$author_link,384$object_link);385case DifferentialAction::ACTION_CLAIM:386return pht(387'%s commandeered %s.',388$author_link,389$object_link);390case DifferentialAction::ACTION_REOPEN:391return pht(392'%s reopened %s.',393$author_link,394$object_link);395}396break;397}398399return parent::getTitleForFeed();400}401402public function getIcon() {403switch ($this->getTransactionType()) {404case self::TYPE_INLINE:405return 'fa-comment';406case self::TYPE_ACTION:407switch ($this->getNewValue()) {408case DifferentialAction::ACTION_CLOSE:409return 'fa-check';410case DifferentialAction::ACTION_ACCEPT:411return 'fa-check-circle-o';412case DifferentialAction::ACTION_REJECT:413return 'fa-times-circle-o';414case DifferentialAction::ACTION_ABANDON:415return 'fa-plane';416case DifferentialAction::ACTION_RETHINK:417return 'fa-headphones';418case DifferentialAction::ACTION_REQUEST:419return 'fa-refresh';420case DifferentialAction::ACTION_RECLAIM:421case DifferentialAction::ACTION_REOPEN:422return 'fa-bullhorn';423case DifferentialAction::ACTION_RESIGN:424return 'fa-flag';425case DifferentialAction::ACTION_CLAIM:426return 'fa-flag';427}428case PhabricatorTransactions::TYPE_EDGE:429switch ($this->getMetadataValue('edge:type')) {430case DifferentialRevisionHasReviewerEdgeType::EDGECONST:431return 'fa-user';432}433}434435return parent::getIcon();436}437438public function shouldDisplayGroupWith(array $group) {439440// Never group status changes with other types of actions, they're indirect441// and don't make sense when combined with direct actions.442443if ($this->isStatusTransaction($this)) {444return false;445}446447foreach ($group as $xaction) {448if ($this->isStatusTransaction($xaction)) {449return false;450}451}452453return parent::shouldDisplayGroupWith($group);454}455456private function isStatusTransaction($xaction) {457$status_type = DifferentialRevisionStatusTransaction::TRANSACTIONTYPE;458if ($xaction->getTransactionType() == $status_type) {459return true;460}461462return false;463}464465466public function getColor() {467switch ($this->getTransactionType()) {468case self::TYPE_ACTION:469switch ($this->getNewValue()) {470case DifferentialAction::ACTION_CLOSE:471return PhabricatorTransactions::COLOR_INDIGO;472case DifferentialAction::ACTION_ACCEPT:473return PhabricatorTransactions::COLOR_GREEN;474case DifferentialAction::ACTION_REJECT:475return PhabricatorTransactions::COLOR_RED;476case DifferentialAction::ACTION_ABANDON:477return PhabricatorTransactions::COLOR_INDIGO;478case DifferentialAction::ACTION_RETHINK:479return PhabricatorTransactions::COLOR_RED;480case DifferentialAction::ACTION_REQUEST:481return PhabricatorTransactions::COLOR_SKY;482case DifferentialAction::ACTION_RECLAIM:483return PhabricatorTransactions::COLOR_SKY;484case DifferentialAction::ACTION_REOPEN:485return PhabricatorTransactions::COLOR_SKY;486case DifferentialAction::ACTION_RESIGN:487return PhabricatorTransactions::COLOR_ORANGE;488case DifferentialAction::ACTION_CLAIM:489return PhabricatorTransactions::COLOR_YELLOW;490}491}492493494return parent::getColor();495}496497public function getNoEffectDescription() {498switch ($this->getTransactionType()) {499case self::TYPE_ACTION:500switch ($this->getNewValue()) {501case DifferentialAction::ACTION_CLOSE:502return pht('This revision is already closed.');503case DifferentialAction::ACTION_ABANDON:504return pht('This revision has already been abandoned.');505case DifferentialAction::ACTION_RECLAIM:506return pht(507'You can not reclaim this revision because his revision is '.508'not abandoned.');509case DifferentialAction::ACTION_REOPEN:510return pht(511'You can not reopen this revision because this revision is '.512'not closed.');513case DifferentialAction::ACTION_RETHINK:514return pht('This revision already requires changes.');515case DifferentialAction::ACTION_CLAIM:516return pht(517'You can not commandeer this revision because you already own '.518'it.');519}520break;521}522523return parent::getNoEffectDescription();524}525526public function renderAsTextForDoorkeeper(527DoorkeeperFeedStoryPublisher $publisher,528PhabricatorFeedStory $story,529array $xactions) {530531$body = parent::renderAsTextForDoorkeeper($publisher, $story, $xactions);532533$inlines = array();534foreach ($xactions as $xaction) {535if ($xaction->getTransactionType() == self::TYPE_INLINE) {536$inlines[] = $xaction;537}538}539540// TODO: This is a bit gross, but far less bad than it used to be. It541// could be further cleaned up at some point.542543if ($inlines) {544$engine = PhabricatorMarkupEngine::newMarkupEngine(array())545->setConfig('viewer', new PhabricatorUser())546->setMode(PhutilRemarkupEngine::MODE_TEXT);547548$body .= "\n\n";549$body .= pht('Inline Comments');550$body .= "\n";551552$changeset_ids = array();553foreach ($inlines as $inline) {554$changeset_ids[] = $inline->getComment()->getChangesetID();555}556557$changesets = id(new DifferentialChangeset())->loadAllWhere(558'id IN (%Ld)',559$changeset_ids);560561foreach ($inlines as $inline) {562$comment = $inline->getComment();563$changeset = idx($changesets, $comment->getChangesetID());564if (!$changeset) {565continue;566}567568$filename = $changeset->getDisplayFilename();569$linenumber = $comment->getLineNumber();570$inline_text = $engine->markupText($comment->getContent());571$inline_text = rtrim($inline_text);572573$body .= "{$filename}:{$linenumber} {$inline_text}\n";574}575}576577return $body;578}579580public function newWarningForTransactions($object, array $xactions) {581$warning = new PhabricatorTransactionWarning();582583switch ($this->getTransactionType()) {584case self::TYPE_INLINE:585$warning->setTitleText(pht('Warning: Editing Inlines'));586$warning->setContinueActionText(pht('Save Inlines and Continue'));587588$count = phutil_count($xactions);589590$body = array();591$body[] = pht(592'You are currently editing %s inline comment(s) on this '.593'revision.',594$count);595$body[] = pht(596'These %s inline comment(s) will be saved and published.',597$count);598599$warning->setWarningParagraphs($body);600break;601case PhabricatorTransactions::TYPE_SUBSCRIBERS:602$warning->setTitleText(pht('Warning: Draft Revision'));603$warning->setContinueActionText(pht('Tell No One'));604605$body = array();606607$body[] = pht(608'This is a draft revision that will not publish any '.609'notifications until the author requests review.');610611$body[] = pht('Mentioned or subscribed users will not be notified.');612613$warning->setWarningParagraphs($body);614break;615}616617return $warning;618}619620621}622623624