[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 final class DifferentialTransaction extends PhabricatorApplicationTransaction { 4 5 private $isCommandeerSideEffect; 6 7 8 public function setIsCommandeerSideEffect($is_side_effect) { 9 $this->isCommandeerSideEffect = $is_side_effect; 10 return $this; 11 } 12 13 public function getIsCommandeerSideEffect() { 14 return $this->isCommandeerSideEffect; 15 } 16 17 const TYPE_INLINE = 'differential:inline'; 18 const TYPE_UPDATE = 'differential:update'; 19 const TYPE_ACTION = 'differential:action'; 20 const TYPE_STATUS = 'differential:status'; 21 22 public function getApplicationName() { 23 return 'differential'; 24 } 25 26 public function getApplicationTransactionType() { 27 return DifferentialRevisionPHIDType::TYPECONST; 28 } 29 30 public function getApplicationTransactionCommentObject() { 31 return new DifferentialTransactionComment(); 32 } 33 34 public function shouldHide() { 35 $old = $this->getOldValue(); 36 $new = $this->getNewValue(); 37 38 switch ($this->getTransactionType()) { 39 case self::TYPE_UPDATE: 40 // Older versions of this transaction have an ID for the new value, 41 // and/or do not record the old value. Only hide the transaction if 42 // the new value is a PHID, indicating that this is a newer style 43 // transaction. 44 if ($old === null) { 45 if (phid_get_type($new) == DifferentialDiffPHIDType::TYPECONST) { 46 return true; 47 } 48 } 49 break; 50 51 case PhabricatorTransactions::TYPE_EDGE: 52 $add = array_diff_key($new, $old); 53 $rem = array_diff_key($old, $new); 54 55 // Hide metadata-only edge transactions. These correspond to users 56 // accepting or rejecting revisions, but the change is always explicit 57 // because of the TYPE_ACTION transaction. Rendering these transactions 58 // just creates clutter. 59 60 if (!$add && !$rem) { 61 return true; 62 } 63 break; 64 } 65 66 return parent::shouldHide(); 67 } 68 69 public function shouldHideForMail(array $xactions) { 70 switch ($this->getTransactionType()) { 71 case self::TYPE_INLINE: 72 // Hide inlines when rendering mail transactions if any other 73 // transaction type exists. 74 foreach ($xactions as $xaction) { 75 if ($xaction->getTransactionType() != self::TYPE_INLINE) { 76 return true; 77 } 78 } 79 80 // If only inline transactions exist, we just render the first one. 81 return ($this !== head($xactions)); 82 } 83 84 return parent::shouldHideForMail($xactions); 85 } 86 87 public function getBodyForMail() { 88 switch ($this->getTransactionType()) { 89 case self::TYPE_INLINE: 90 // Don't render inlines into the mail body; they render into a special 91 // section immediately after the body instead. 92 return null; 93 } 94 95 return parent::getBodyForMail(); 96 } 97 98 public function getRequiredHandlePHIDs() { 99 $phids = parent::getRequiredHandlePHIDs(); 100 101 $old = $this->getOldValue(); 102 $new = $this->getNewValue(); 103 104 switch ($this->getTransactionType()) { 105 case self::TYPE_ACTION: 106 if ($new == DifferentialAction::ACTION_CLOSE && 107 $this->getMetadataValue('isCommitClose')) { 108 $phids[] = $this->getMetadataValue('commitPHID'); 109 if ($this->getMetadataValue('committerPHID')) { 110 $phids[] = $this->getMetadataValue('committerPHID'); 111 } 112 if ($this->getMetadataValue('authorPHID')) { 113 $phids[] = $this->getMetadataValue('authorPHID'); 114 } 115 } 116 break; 117 case self::TYPE_UPDATE: 118 if ($new) { 119 $phids[] = $new; 120 } 121 break; 122 } 123 124 return $phids; 125 } 126 127 public function getActionStrength() { 128 129 switch ($this->getTransactionType()) { 130 case self::TYPE_ACTION: 131 return 3; 132 case self::TYPE_UPDATE: 133 return 2; 134 case self::TYPE_INLINE: 135 return 0.25; 136 } 137 138 return parent::getActionStrength(); 139 } 140 141 142 public function getActionName() { 143 switch ($this->getTransactionType()) { 144 case self::TYPE_INLINE: 145 return pht('Commented On'); 146 case self::TYPE_UPDATE: 147 $old = $this->getOldValue(); 148 if ($old === null) { 149 return pht('Request'); 150 } else { 151 return pht('Updated'); 152 } 153 case self::TYPE_ACTION: 154 $map = array( 155 DifferentialAction::ACTION_ACCEPT => pht('Accepted'), 156 DifferentialAction::ACTION_REJECT => pht('Requested Changes To'), 157 DifferentialAction::ACTION_RETHINK => pht('Planned Changes To'), 158 DifferentialAction::ACTION_ABANDON => pht('Abandoned'), 159 DifferentialAction::ACTION_CLOSE => pht('Closed'), 160 DifferentialAction::ACTION_REQUEST => pht('Requested A Review Of'), 161 DifferentialAction::ACTION_RESIGN => pht('Resigned From'), 162 DifferentialAction::ACTION_ADDREVIEWERS => pht('Added Reviewers'), 163 DifferentialAction::ACTION_CLAIM => pht('Commandeered'), 164 DifferentialAction::ACTION_REOPEN => pht('Reopened'), 165 ); 166 $name = idx($map, $this->getNewValue()); 167 if ($name !== null) { 168 return $name; 169 } 170 break; 171 } 172 173 return parent::getActionName(); 174 } 175 176 public function getMailTags() { 177 $tags = array(); 178 179 switch ($this->getTransactionType()) { 180 case PhabricatorTransactions::TYPE_SUBSCRIBERS; 181 $tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_CC; 182 break; 183 case self::TYPE_ACTION: 184 switch ($this->getNewValue()) { 185 case DifferentialAction::ACTION_CLOSE: 186 $tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_CLOSED; 187 break; 188 } 189 break; 190 case self::TYPE_UPDATE: 191 $old = $this->getOldValue(); 192 if ($old === null) { 193 $tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEW_REQUEST; 194 } else { 195 $tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_UPDATED; 196 } 197 break; 198 case PhabricatorTransactions::TYPE_EDGE: 199 switch ($this->getMetadataValue('edge:type')) { 200 case PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER: 201 $tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEWERS; 202 break; 203 } 204 break; 205 case PhabricatorTransactions::TYPE_COMMENT: 206 case self::TYPE_INLINE: 207 $tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_COMMENT; 208 break; 209 } 210 211 if (!$tags) { 212 $tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_OTHER; 213 } 214 215 return $tags; 216 } 217 218 public function getTitle() { 219 $author_phid = $this->getAuthorPHID(); 220 $author_handle = $this->renderHandleLink($author_phid); 221 222 $old = $this->getOldValue(); 223 $new = $this->getNewValue(); 224 225 switch ($this->getTransactionType()) { 226 case self::TYPE_INLINE: 227 return pht( 228 '%s added inline comments.', 229 $author_handle); 230 case self::TYPE_UPDATE: 231 if ($this->getMetadataValue('isCommitUpdate')) { 232 return pht( 233 'This revision was automatically updated to reflect the '. 234 'committed changes.'); 235 } else if ($new) { 236 // TODO: Migrate to PHIDs and use handles here? 237 if (phid_get_type($new) == DifferentialDiffPHIDType::TYPECONST) { 238 return pht( 239 '%s updated this revision to %s.', 240 $author_handle, 241 $this->renderHandleLink($new)); 242 } else { 243 return pht( 244 '%s updated this revision.', 245 $author_handle); 246 } 247 } else { 248 return pht( 249 '%s updated this revision.', 250 $author_handle); 251 } 252 case self::TYPE_ACTION: 253 switch ($new) { 254 case DifferentialAction::ACTION_CLOSE: 255 if (!$this->getMetadataValue('isCommitClose')) { 256 return DifferentialAction::getBasicStoryText( 257 $new, 258 $author_handle); 259 } 260 $commit_name = $this->renderHandleLink( 261 $this->getMetadataValue('commitPHID')); 262 $committer_phid = $this->getMetadataValue('committerPHID'); 263 $author_phid = $this->getMetadataValue('authorPHID'); 264 if ($this->getHandleIfExists($committer_phid)) { 265 $committer_name = $this->renderHandleLink($committer_phid); 266 } else { 267 $committer_name = $this->getMetadataValue('committerName'); 268 } 269 if ($this->getHandleIfExists($author_phid)) { 270 $author_name = $this->renderHandleLink($author_phid); 271 } else { 272 $author_name = $this->getMetadataValue('authorName'); 273 } 274 275 if ($committer_name && ($committer_name != $author_name)) { 276 return pht( 277 'Closed by commit %s (authored by %s, committed by %s).', 278 $commit_name, 279 $author_name, 280 $committer_name); 281 } else { 282 return pht( 283 'Closed by commit %s (authored by %s).', 284 $commit_name, 285 $author_name); 286 } 287 break; 288 default: 289 return DifferentialAction::getBasicStoryText($new, $author_handle); 290 } 291 break; 292 case self::TYPE_STATUS: 293 switch ($this->getNewValue()) { 294 case ArcanistDifferentialRevisionStatus::ACCEPTED: 295 return pht( 296 'This revision is now accepted and ready to land.'); 297 case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: 298 return pht( 299 'This revision now requires changes to proceed.'); 300 case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: 301 return pht( 302 'This revision now requires review to proceed.'); 303 } 304 } 305 306 return parent::getTitle(); 307 } 308 309 public function renderExtraInformationLink() { 310 if ($this->getMetadataValue('revisionMatchData')) { 311 $details_href = 312 '/differential/revision/closedetails/'.$this->getPHID().'/'; 313 $details_link = javelin_tag( 314 'a', 315 array( 316 'href' => $details_href, 317 'sigil' => 'workflow', 318 ), 319 pht('Explain Why')); 320 return $details_link; 321 } 322 return parent::renderExtraInformationLink(); 323 } 324 325 public function getTitleForFeed(PhabricatorFeedStory $story) { 326 $author_phid = $this->getAuthorPHID(); 327 $object_phid = $this->getObjectPHID(); 328 329 $old = $this->getOldValue(); 330 $new = $this->getNewValue(); 331 332 $author_link = $this->renderHandleLink($author_phid); 333 $object_link = $this->renderHandleLink($object_phid); 334 335 switch ($this->getTransactionType()) { 336 case self::TYPE_INLINE: 337 return pht( 338 '%s added inline comments to %s.', 339 $author_link, 340 $object_link); 341 case self::TYPE_UPDATE: 342 return pht( 343 '%s updated the diff for %s.', 344 $author_link, 345 $object_link); 346 case self::TYPE_ACTION: 347 switch ($new) { 348 case DifferentialAction::ACTION_ACCEPT: 349 return pht( 350 '%s accepted %s.', 351 $author_link, 352 $object_link); 353 case DifferentialAction::ACTION_REJECT: 354 return pht( 355 '%s requested changes to %s.', 356 $author_link, 357 $object_link); 358 case DifferentialAction::ACTION_RETHINK: 359 return pht( 360 '%s planned changes to %s.', 361 $author_link, 362 $object_link); 363 case DifferentialAction::ACTION_ABANDON: 364 return pht( 365 '%s abandoned %s.', 366 $author_link, 367 $object_link); 368 case DifferentialAction::ACTION_CLOSE: 369 if (!$this->getMetadataValue('isCommitClose')) { 370 return pht( 371 '%s closed %s.', 372 $author_link, 373 $object_link); 374 } else { 375 $commit_name = $this->renderHandleLink( 376 $this->getMetadataValue('commitPHID')); 377 $committer_phid = $this->getMetadataValue('committerPHID'); 378 $author_phid = $this->getMetadataValue('authorPHID'); 379 if ($this->getHandleIfExists($committer_phid)) { 380 $committer_name = $this->renderHandleLink($committer_phid); 381 } else { 382 $committer_name = $this->getMetadataValue('committerName'); 383 } 384 if ($this->getHandleIfExists($author_phid)) { 385 $author_name = $this->renderHandleLink($author_phid); 386 } else { 387 $author_name = $this->getMetadataValue('authorName'); 388 } 389 390 if ($committer_name && ($committer_name != $author_name)) { 391 return pht( 392 '%s closed %s by commit %s (authored by %s).', 393 $author_link, 394 $object_link, 395 $commit_name, 396 $author_name); 397 } else { 398 return pht( 399 '%s closed %s by commit %s.', 400 $author_link, 401 $object_link, 402 $commit_name); 403 } 404 } 405 break; 406 407 case DifferentialAction::ACTION_REQUEST: 408 return pht( 409 '%s requested review of %s.', 410 $author_link, 411 $object_link); 412 case DifferentialAction::ACTION_RECLAIM: 413 return pht( 414 '%s reclaimed %s.', 415 $author_link, 416 $object_link); 417 case DifferentialAction::ACTION_RESIGN: 418 return pht( 419 '%s resigned from %s.', 420 $author_link, 421 $object_link); 422 case DifferentialAction::ACTION_CLAIM: 423 return pht( 424 '%s commandeered %s.', 425 $author_link, 426 $object_link); 427 case DifferentialAction::ACTION_REOPEN: 428 return pht( 429 '%s reopened %s.', 430 $author_link, 431 $object_link); 432 } 433 break; 434 case self::TYPE_STATUS: 435 switch ($this->getNewValue()) { 436 case ArcanistDifferentialRevisionStatus::ACCEPTED: 437 return pht( 438 '%s is now accepted and ready to land.', 439 $object_link); 440 case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: 441 return pht( 442 '%s now requires changes to proceed.', 443 $object_link); 444 case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: 445 return pht( 446 '%s now requires review to proceed.', 447 $object_link); 448 } 449 } 450 451 return parent::getTitleForFeed($story); 452 } 453 454 public function getIcon() { 455 switch ($this->getTransactionType()) { 456 case self::TYPE_INLINE: 457 return 'fa-comment'; 458 case self::TYPE_UPDATE: 459 return 'fa-refresh'; 460 case self::TYPE_STATUS: 461 switch ($this->getNewValue()) { 462 case ArcanistDifferentialRevisionStatus::ACCEPTED: 463 return 'fa-check'; 464 case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: 465 return 'fa-times'; 466 case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: 467 return 'fa-undo'; 468 } 469 break; 470 case self::TYPE_ACTION: 471 switch ($this->getNewValue()) { 472 case DifferentialAction::ACTION_CLOSE: 473 return 'fa-check'; 474 case DifferentialAction::ACTION_ACCEPT: 475 return 'fa-check-circle-o'; 476 case DifferentialAction::ACTION_REJECT: 477 return 'fa-times-circle-o'; 478 case DifferentialAction::ACTION_ABANDON: 479 return 'fa-plane'; 480 case DifferentialAction::ACTION_RETHINK: 481 return 'fa-headphones'; 482 case DifferentialAction::ACTION_REQUEST: 483 return 'fa-refresh'; 484 case DifferentialAction::ACTION_RECLAIM: 485 case DifferentialAction::ACTION_REOPEN: 486 return 'fa-bullhorn'; 487 case DifferentialAction::ACTION_RESIGN: 488 return 'fa-flag'; 489 case DifferentialAction::ACTION_CLAIM: 490 return 'fa-flag'; 491 } 492 case PhabricatorTransactions::TYPE_EDGE: 493 switch ($this->getMetadataValue('edge:type')) { 494 case PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER: 495 return 'fa-user'; 496 } 497 } 498 499 return parent::getIcon(); 500 } 501 502 public function shouldDisplayGroupWith(array $group) { 503 504 // Never group status changes with other types of actions, they're indirect 505 // and don't make sense when combined with direct actions. 506 507 $type_status = self::TYPE_STATUS; 508 509 if ($this->getTransactionType() == $type_status) { 510 return false; 511 } 512 513 foreach ($group as $xaction) { 514 if ($xaction->getTransactionType() == $type_status) { 515 return false; 516 } 517 } 518 519 return parent::shouldDisplayGroupWith($group); 520 } 521 522 523 public function getColor() { 524 switch ($this->getTransactionType()) { 525 case self::TYPE_UPDATE: 526 return PhabricatorTransactions::COLOR_SKY; 527 case self::TYPE_STATUS: 528 switch ($this->getNewValue()) { 529 case ArcanistDifferentialRevisionStatus::ACCEPTED: 530 return PhabricatorTransactions::COLOR_GREEN; 531 case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: 532 return PhabricatorTransactions::COLOR_RED; 533 case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: 534 return PhabricatorTransactions::COLOR_ORANGE; 535 } 536 break; 537 case self::TYPE_ACTION: 538 switch ($this->getNewValue()) { 539 case DifferentialAction::ACTION_CLOSE: 540 return PhabricatorTransactions::COLOR_BLUE; 541 case DifferentialAction::ACTION_ACCEPT: 542 return PhabricatorTransactions::COLOR_GREEN; 543 case DifferentialAction::ACTION_REJECT: 544 return PhabricatorTransactions::COLOR_RED; 545 case DifferentialAction::ACTION_ABANDON: 546 return PhabricatorTransactions::COLOR_BLACK; 547 case DifferentialAction::ACTION_RETHINK: 548 return PhabricatorTransactions::COLOR_RED; 549 case DifferentialAction::ACTION_REQUEST: 550 return PhabricatorTransactions::COLOR_SKY; 551 case DifferentialAction::ACTION_RECLAIM: 552 return PhabricatorTransactions::COLOR_SKY; 553 case DifferentialAction::ACTION_REOPEN: 554 return PhabricatorTransactions::COLOR_SKY; 555 case DifferentialAction::ACTION_RESIGN: 556 return PhabricatorTransactions::COLOR_ORANGE; 557 case DifferentialAction::ACTION_CLAIM: 558 return PhabricatorTransactions::COLOR_YELLOW; 559 } 560 } 561 562 563 return parent::getColor(); 564 } 565 566 public function getNoEffectDescription() { 567 switch ($this->getTransactionType()) { 568 case PhabricatorTransactions::TYPE_EDGE: 569 switch ($this->getMetadataValue('edge:type')) { 570 case PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER: 571 return pht( 572 'The reviewers you are trying to add are already reviewing '. 573 'this revision.'); 574 } 575 break; 576 case DifferentialTransaction::TYPE_ACTION: 577 switch ($this->getNewValue()) { 578 case DifferentialAction::ACTION_CLOSE: 579 return pht('This revision is already closed.'); 580 case DifferentialAction::ACTION_ABANDON: 581 return pht('This revision has already been abandoned.'); 582 case DifferentialAction::ACTION_RECLAIM: 583 return pht( 584 'You can not reclaim this revision because his revision is '. 585 'not abandoned.'); 586 case DifferentialAction::ACTION_REOPEN: 587 return pht( 588 'You can not reopen this revision because this revision is '. 589 'not closed.'); 590 case DifferentialAction::ACTION_RETHINK: 591 return pht('This revision already requires changes.'); 592 case DifferentialAction::ACTION_REQUEST: 593 return pht('Review is already requested for this revision.'); 594 case DifferentialAction::ACTION_RESIGN: 595 return pht( 596 'You can not resign from this revision because you are not '. 597 'a reviewer.'); 598 case DifferentialAction::ACTION_CLAIM: 599 return pht( 600 'You can not commandeer this revision because you already own '. 601 'it.'); 602 case DifferentialAction::ACTION_ACCEPT: 603 return pht( 604 'You have already accepted this revision.'); 605 case DifferentialAction::ACTION_REJECT: 606 return pht( 607 'You have already requested changes to this revision.'); 608 } 609 break; 610 } 611 612 return parent::getNoEffectDescription(); 613 } 614 615 public function renderAsTextForDoorkeeper( 616 DoorkeeperFeedStoryPublisher $publisher, 617 PhabricatorFeedStory $story, 618 array $xactions) { 619 620 $body = parent::renderAsTextForDoorkeeper($publisher, $story, $xactions); 621 622 $inlines = array(); 623 foreach ($xactions as $xaction) { 624 if ($xaction->getTransactionType() == self::TYPE_INLINE) { 625 $inlines[] = $xaction; 626 } 627 } 628 629 // TODO: This is a bit gross, but far less bad than it used to be. It 630 // could be further cleaned up at some point. 631 632 if ($inlines) { 633 $engine = PhabricatorMarkupEngine::newMarkupEngine(array()) 634 ->setConfig('viewer', new PhabricatorUser()) 635 ->setMode(PhutilRemarkupEngine::MODE_TEXT); 636 637 $body .= "\n\n"; 638 $body .= pht('Inline Comments'); 639 $body .= "\n"; 640 641 $changeset_ids = array(); 642 foreach ($inlines as $inline) { 643 $changeset_ids[] = $inline->getComment()->getChangesetID(); 644 } 645 646 $changesets = id(new DifferentialChangeset())->loadAllWhere( 647 'id IN (%Ld)', 648 $changeset_ids); 649 650 foreach ($inlines as $inline) { 651 $comment = $inline->getComment(); 652 $changeset = idx($changesets, $comment->getChangesetID()); 653 if (!$changeset) { 654 continue; 655 } 656 657 $filename = $changeset->getDisplayFilename(); 658 $linenumber = $comment->getLineNumber(); 659 $inline_text = $engine->markupText($comment->getContent()); 660 $inline_text = rtrim($inline_text); 661 662 $body .= "{$filename}:{$linenumber} {$inline_text}\n"; 663 } 664 } 665 666 return $body; 667 } 668 669 670 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |