[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 abstract class PhabricatorApplicationTransaction 4 extends PhabricatorLiskDAO 5 implements 6 PhabricatorPolicyInterface, 7 PhabricatorDestructibleInterface { 8 9 const TARGET_TEXT = 'text'; 10 const TARGET_HTML = 'html'; 11 12 protected $phid; 13 protected $objectPHID; 14 protected $authorPHID; 15 protected $viewPolicy; 16 protected $editPolicy; 17 18 protected $commentPHID; 19 protected $commentVersion = 0; 20 protected $transactionType; 21 protected $oldValue; 22 protected $newValue; 23 protected $metadata = array(); 24 25 protected $contentSource; 26 27 private $comment; 28 private $commentNotLoaded; 29 30 private $handles; 31 private $renderingTarget = self::TARGET_HTML; 32 private $transactionGroup = array(); 33 private $viewer = self::ATTACHABLE; 34 private $object = self::ATTACHABLE; 35 private $oldValueHasBeenSet = false; 36 37 private $ignoreOnNoEffect; 38 39 40 /** 41 * Flag this transaction as a pure side-effect which should be ignored when 42 * applying transactions if it has no effect, even if transaction application 43 * would normally fail. This both provides users with better error messages 44 * and allows transactions to perform optional side effects. 45 */ 46 public function setIgnoreOnNoEffect($ignore) { 47 $this->ignoreOnNoEffect = $ignore; 48 return $this; 49 } 50 51 public function getIgnoreOnNoEffect() { 52 return $this->ignoreOnNoEffect; 53 } 54 55 public function shouldGenerateOldValue() { 56 switch ($this->getTransactionType()) { 57 case PhabricatorTransactions::TYPE_BUILDABLE: 58 case PhabricatorTransactions::TYPE_TOKEN: 59 case PhabricatorTransactions::TYPE_CUSTOMFIELD: 60 return false; 61 } 62 return true; 63 } 64 65 abstract public function getApplicationTransactionType(); 66 67 private function getApplicationObjectTypeName() { 68 $types = PhabricatorPHIDType::getAllTypes(); 69 70 $type = idx($types, $this->getApplicationTransactionType()); 71 if ($type) { 72 return $type->getTypeName(); 73 } 74 75 return pht('Object'); 76 } 77 78 public function getApplicationTransactionCommentObject() { 79 throw new PhutilMethodNotImplementedException(); 80 } 81 82 public function getApplicationTransactionViewObject() { 83 return new PhabricatorApplicationTransactionView(); 84 } 85 86 public function getMetadataValue($key, $default = null) { 87 return idx($this->metadata, $key, $default); 88 } 89 90 public function setMetadataValue($key, $value) { 91 $this->metadata[$key] = $value; 92 return $this; 93 } 94 95 public function generatePHID() { 96 $type = PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST; 97 $subtype = $this->getApplicationTransactionType(); 98 99 return PhabricatorPHID::generateNewPHID($type, $subtype); 100 } 101 102 public function getConfiguration() { 103 return array( 104 self::CONFIG_AUX_PHID => true, 105 self::CONFIG_SERIALIZATION => array( 106 'oldValue' => self::SERIALIZATION_JSON, 107 'newValue' => self::SERIALIZATION_JSON, 108 'metadata' => self::SERIALIZATION_JSON, 109 ), 110 self::CONFIG_COLUMN_SCHEMA => array( 111 'commentPHID' => 'phid?', 112 'commentVersion' => 'uint32', 113 'contentSource' => 'text', 114 'transactionType' => 'text32', 115 ), 116 self::CONFIG_KEY_SCHEMA => array( 117 'key_object' => array( 118 'columns' => array('objectPHID'), 119 ), 120 ), 121 ) + parent::getConfiguration(); 122 } 123 124 public function setContentSource(PhabricatorContentSource $content_source) { 125 $this->contentSource = $content_source->serialize(); 126 return $this; 127 } 128 129 public function getContentSource() { 130 return PhabricatorContentSource::newFromSerialized($this->contentSource); 131 } 132 133 public function hasComment() { 134 return $this->getComment() && strlen($this->getComment()->getContent()); 135 } 136 137 public function getComment() { 138 if ($this->commentNotLoaded) { 139 throw new Exception('Comment for this transaction was not loaded.'); 140 } 141 return $this->comment; 142 } 143 144 public function attachComment( 145 PhabricatorApplicationTransactionComment $comment) { 146 $this->comment = $comment; 147 $this->commentNotLoaded = false; 148 return $this; 149 } 150 151 public function setCommentNotLoaded($not_loaded) { 152 $this->commentNotLoaded = $not_loaded; 153 return $this; 154 } 155 156 public function attachObject($object) { 157 $this->object = $object; 158 return $this; 159 } 160 161 public function getObject() { 162 return $this->assertAttached($this->object); 163 } 164 165 public function getRemarkupBlocks() { 166 $blocks = array(); 167 168 switch ($this->getTransactionType()) { 169 case PhabricatorTransactions::TYPE_CUSTOMFIELD: 170 $field = $this->getTransactionCustomField(); 171 if ($field) { 172 $custom_blocks = $field->getApplicationTransactionRemarkupBlocks( 173 $this); 174 foreach ($custom_blocks as $custom_block) { 175 $blocks[] = $custom_block; 176 } 177 } 178 break; 179 } 180 181 if ($this->getComment()) { 182 $blocks[] = $this->getComment()->getContent(); 183 } 184 185 return $blocks; 186 } 187 188 public function setOldValue($value) { 189 $this->oldValueHasBeenSet = true; 190 $this->writeField('oldValue', $value); 191 return $this; 192 } 193 194 public function hasOldValue() { 195 return $this->oldValueHasBeenSet; 196 } 197 198 199 /* -( Rendering )---------------------------------------------------------- */ 200 201 public function setRenderingTarget($rendering_target) { 202 $this->renderingTarget = $rendering_target; 203 return $this; 204 } 205 206 public function getRenderingTarget() { 207 return $this->renderingTarget; 208 } 209 210 public function attachViewer(PhabricatorUser $viewer) { 211 $this->viewer = $viewer; 212 return $this; 213 } 214 215 public function getViewer() { 216 return $this->assertAttached($this->viewer); 217 } 218 219 public function getRequiredHandlePHIDs() { 220 $phids = array(); 221 222 $old = $this->getOldValue(); 223 $new = $this->getNewValue(); 224 225 $phids[] = array($this->getAuthorPHID()); 226 switch ($this->getTransactionType()) { 227 case PhabricatorTransactions::TYPE_CUSTOMFIELD: 228 $field = $this->getTransactionCustomField(); 229 if ($field) { 230 $phids[] = $field->getApplicationTransactionRequiredHandlePHIDs( 231 $this); 232 } 233 break; 234 case PhabricatorTransactions::TYPE_SUBSCRIBERS: 235 $phids[] = $old; 236 $phids[] = $new; 237 break; 238 case PhabricatorTransactions::TYPE_EDGE: 239 $phids[] = ipull($old, 'dst'); 240 $phids[] = ipull($new, 'dst'); 241 break; 242 case PhabricatorTransactions::TYPE_EDIT_POLICY: 243 case PhabricatorTransactions::TYPE_VIEW_POLICY: 244 case PhabricatorTransactions::TYPE_JOIN_POLICY: 245 if (!PhabricatorPolicyQuery::isGlobalPolicy($old)) { 246 $phids[] = array($old); 247 } 248 if (!PhabricatorPolicyQuery::isGlobalPolicy($new)) { 249 $phids[] = array($new); 250 } 251 break; 252 case PhabricatorTransactions::TYPE_TOKEN: 253 break; 254 case PhabricatorTransactions::TYPE_BUILDABLE: 255 $phid = $this->getMetadataValue('harbormaster:buildablePHID'); 256 if ($phid) { 257 $phids[] = array($phid); 258 } 259 break; 260 } 261 262 if ($this->getComment()) { 263 $phids[] = array($this->getComment()->getAuthorPHID()); 264 } 265 266 return array_mergev($phids); 267 } 268 269 public function setHandles(array $handles) { 270 $this->handles = $handles; 271 return $this; 272 } 273 274 public function getHandle($phid) { 275 if (empty($this->handles[$phid])) { 276 throw new Exception( 277 pht( 278 'Transaction ("%s", of type "%s") requires a handle ("%s") that it '. 279 'did not load.', 280 $this->getPHID(), 281 $this->getTransactionType(), 282 $phid)); 283 } 284 return $this->handles[$phid]; 285 } 286 287 public function getHandleIfExists($phid) { 288 return idx($this->handles, $phid); 289 } 290 291 public function getHandles() { 292 if ($this->handles === null) { 293 throw new Exception( 294 'Transaction requires handles and it did not load them.' 295 ); 296 } 297 return $this->handles; 298 } 299 300 public function renderHandleLink($phid) { 301 if ($this->renderingTarget == self::TARGET_HTML) { 302 return $this->getHandle($phid)->renderLink(); 303 } else { 304 return $this->getHandle($phid)->getLinkName(); 305 } 306 } 307 308 public function renderHandleList(array $phids) { 309 $links = array(); 310 foreach ($phids as $phid) { 311 $links[] = $this->renderHandleLink($phid); 312 } 313 if ($this->renderingTarget == self::TARGET_HTML) { 314 return phutil_implode_html(', ', $links); 315 } else { 316 return implode(', ', $links); 317 } 318 } 319 320 private function renderSubscriberList(array $phids, $change_type) { 321 if ($this->getRenderingTarget() == self::TARGET_TEXT) { 322 return $this->renderHandleList($phids); 323 } else { 324 $handles = array_select_keys($this->getHandles(), $phids); 325 return id(new SubscriptionListStringBuilder()) 326 ->setHandles($handles) 327 ->setObjectPHID($this->getPHID()) 328 ->buildTransactionString($change_type); 329 } 330 } 331 332 protected function renderPolicyName($phid, $state = 'old') { 333 $policy = PhabricatorPolicy::newFromPolicyAndHandle( 334 $phid, 335 $this->getHandleIfExists($phid)); 336 if ($this->renderingTarget == self::TARGET_HTML) { 337 switch ($policy->getType()) { 338 case PhabricatorPolicyType::TYPE_CUSTOM: 339 $policy->setHref('/transactions/'.$state.'/'.$this->getPHID().'/'); 340 $policy->setWorkflow(true); 341 break; 342 default: 343 break; 344 } 345 $output = $policy->renderDescription(); 346 } else { 347 $output = hsprintf('%s', $policy->getFullName()); 348 } 349 return $output; 350 } 351 352 public function getIcon() { 353 switch ($this->getTransactionType()) { 354 case PhabricatorTransactions::TYPE_COMMENT: 355 $comment = $this->getComment(); 356 if ($comment && $comment->getIsRemoved()) { 357 return 'fa-eraser'; 358 } 359 return 'fa-comment'; 360 case PhabricatorTransactions::TYPE_SUBSCRIBERS: 361 return 'fa-envelope'; 362 case PhabricatorTransactions::TYPE_VIEW_POLICY: 363 case PhabricatorTransactions::TYPE_EDIT_POLICY: 364 case PhabricatorTransactions::TYPE_JOIN_POLICY: 365 return 'fa-lock'; 366 case PhabricatorTransactions::TYPE_EDGE: 367 return 'fa-link'; 368 case PhabricatorTransactions::TYPE_BUILDABLE: 369 return 'fa-wrench'; 370 case PhabricatorTransactions::TYPE_TOKEN: 371 return 'fa-trophy'; 372 } 373 374 return 'fa-pencil'; 375 } 376 377 public function getToken() { 378 switch ($this->getTransactionType()) { 379 case PhabricatorTransactions::TYPE_TOKEN: 380 $old = $this->getOldValue(); 381 $new = $this->getNewValue(); 382 if ($new) { 383 $icon = substr($new, 10); 384 } else { 385 $icon = substr($old, 10); 386 } 387 return array($icon, !$this->getNewValue()); 388 } 389 390 return array(null, null); 391 } 392 393 public function getColor() { 394 switch ($this->getTransactionType()) { 395 case PhabricatorTransactions::TYPE_COMMENT; 396 $comment = $this->getComment(); 397 if ($comment && $comment->getIsRemoved()) { 398 return 'black'; 399 } 400 break; 401 case PhabricatorTransactions::TYPE_BUILDABLE: 402 switch ($this->getNewValue()) { 403 case HarbormasterBuildable::STATUS_PASSED: 404 return 'green'; 405 case HarbormasterBuildable::STATUS_FAILED: 406 return 'red'; 407 } 408 break; 409 } 410 return null; 411 } 412 413 protected function getTransactionCustomField() { 414 switch ($this->getTransactionType()) { 415 case PhabricatorTransactions::TYPE_CUSTOMFIELD: 416 $key = $this->getMetadataValue('customfield:key'); 417 if (!$key) { 418 return null; 419 } 420 421 $field = PhabricatorCustomField::getObjectField( 422 $this->getObject(), 423 PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS, 424 $key); 425 if (!$field) { 426 return null; 427 } 428 429 $field->setViewer($this->getViewer()); 430 return $field; 431 } 432 433 return null; 434 } 435 436 public function shouldHide() { 437 switch ($this->getTransactionType()) { 438 case PhabricatorTransactions::TYPE_VIEW_POLICY: 439 case PhabricatorTransactions::TYPE_EDIT_POLICY: 440 case PhabricatorTransactions::TYPE_JOIN_POLICY: 441 if ($this->getOldValue() === null) { 442 return true; 443 } else { 444 return false; 445 } 446 break; 447 case PhabricatorTransactions::TYPE_CUSTOMFIELD: 448 $field = $this->getTransactionCustomField(); 449 if ($field) { 450 return $field->shouldHideInApplicationTransactions($this); 451 } 452 case PhabricatorTransactions::TYPE_EDGE: 453 $edge_type = $this->getMetadataValue('edge:type'); 454 switch ($edge_type) { 455 case PhabricatorObjectMentionsObject::EDGECONST: 456 return true; 457 break; 458 case PhabricatorObjectMentionedByObject::EDGECONST: 459 $new = ipull($this->getNewValue(), 'dst'); 460 $old = ipull($this->getOldValue(), 'dst'); 461 $add = array_diff($new, $old); 462 $add_value = reset($add); 463 $add_handle = $this->getHandle($add_value); 464 if ($add_handle->getPolicyFiltered()) { 465 return true; 466 } 467 return false; 468 break; 469 default: 470 break; 471 } 472 break; 473 } 474 475 return false; 476 } 477 478 public function shouldHideForMail(array $xactions) { 479 switch ($this->getTransactionType()) { 480 case PhabricatorTransactions::TYPE_TOKEN: 481 return true; 482 case PhabricatorTransactions::TYPE_BUILDABLE: 483 switch ($this->getNewValue()) { 484 case HarbormasterBuildable::STATUS_FAILED: 485 // For now, only ever send mail when builds fail. We might let 486 // you customize this later, but in most cases this is probably 487 // completely uninteresting. 488 return false; 489 } 490 return true; 491 case PhabricatorTransactions::TYPE_EDGE: 492 $edge_type = $this->getMetadataValue('edge:type'); 493 switch ($edge_type) { 494 case PhabricatorObjectMentionsObject::EDGECONST: 495 case PhabricatorObjectMentionedByObject::EDGECONST: 496 return true; 497 break; 498 default: 499 break; 500 } 501 break; 502 } 503 504 return $this->shouldHide(); 505 } 506 507 public function shouldHideForFeed() { 508 switch ($this->getTransactionType()) { 509 case PhabricatorTransactions::TYPE_TOKEN: 510 return true; 511 case PhabricatorTransactions::TYPE_BUILDABLE: 512 switch ($this->getNewValue()) { 513 case HarbormasterBuildable::STATUS_FAILED: 514 // For now, don't notify on build passes either. These are pretty 515 // high volume and annoying, with very little present value. We 516 // might want to turn them back on in the specific case of 517 // build successes on the current document? 518 return false; 519 } 520 return true; 521 case PhabricatorTransactions::TYPE_EDGE: 522 $edge_type = $this->getMetadataValue('edge:type'); 523 switch ($edge_type) { 524 case PhabricatorObjectMentionsObject::EDGECONST: 525 case PhabricatorObjectMentionedByObject::EDGECONST: 526 return true; 527 break; 528 default: 529 break; 530 } 531 break; 532 } 533 534 return $this->shouldHide(); 535 } 536 537 public function getTitleForMail() { 538 return id(clone $this)->setRenderingTarget('text')->getTitle(); 539 } 540 541 public function getBodyForMail() { 542 $comment = $this->getComment(); 543 if ($comment && strlen($comment->getContent())) { 544 return $comment->getContent(); 545 } 546 return null; 547 } 548 549 public function getNoEffectDescription() { 550 551 switch ($this->getTransactionType()) { 552 case PhabricatorTransactions::TYPE_COMMENT: 553 return pht('You can not post an empty comment.'); 554 case PhabricatorTransactions::TYPE_VIEW_POLICY: 555 return pht( 556 'This %s already has that view policy.', 557 $this->getApplicationObjectTypeName()); 558 case PhabricatorTransactions::TYPE_EDIT_POLICY: 559 return pht( 560 'This %s already has that edit policy.', 561 $this->getApplicationObjectTypeName()); 562 case PhabricatorTransactions::TYPE_JOIN_POLICY: 563 return pht( 564 'This %s already has that join policy.', 565 $this->getApplicationObjectTypeName()); 566 case PhabricatorTransactions::TYPE_SUBSCRIBERS: 567 return pht( 568 'All users are already subscribed to this %s.', 569 $this->getApplicationObjectTypeName()); 570 case PhabricatorTransactions::TYPE_EDGE: 571 return pht('Edges already exist; transaction has no effect.'); 572 } 573 574 return pht('Transaction has no effect.'); 575 } 576 577 public function getTitle() { 578 $author_phid = $this->getAuthorPHID(); 579 580 $old = $this->getOldValue(); 581 $new = $this->getNewValue(); 582 583 switch ($this->getTransactionType()) { 584 case PhabricatorTransactions::TYPE_COMMENT: 585 return pht( 586 '%s added a comment.', 587 $this->renderHandleLink($author_phid)); 588 case PhabricatorTransactions::TYPE_VIEW_POLICY: 589 return pht( 590 '%s changed the visibility of this %s from "%s" to "%s".', 591 $this->renderHandleLink($author_phid), 592 $this->getApplicationObjectTypeName(), 593 $this->renderPolicyName($old, 'old'), 594 $this->renderPolicyName($new, 'new')); 595 case PhabricatorTransactions::TYPE_EDIT_POLICY: 596 return pht( 597 '%s changed the edit policy of this %s from "%s" to "%s".', 598 $this->renderHandleLink($author_phid), 599 $this->getApplicationObjectTypeName(), 600 $this->renderPolicyName($old, 'old'), 601 $this->renderPolicyName($new, 'new')); 602 case PhabricatorTransactions::TYPE_JOIN_POLICY: 603 return pht( 604 '%s changed the join policy of this %s from "%s" to "%s".', 605 $this->renderHandleLink($author_phid), 606 $this->getApplicationObjectTypeName(), 607 $this->renderPolicyName($old, 'old'), 608 $this->renderPolicyName($new, 'new')); 609 case PhabricatorTransactions::TYPE_SUBSCRIBERS: 610 $add = array_diff($new, $old); 611 $rem = array_diff($old, $new); 612 613 if ($add && $rem) { 614 return pht( 615 '%s edited subscriber(s), added %d: %s; removed %d: %s.', 616 $this->renderHandleLink($author_phid), 617 count($add), 618 $this->renderSubscriberList($add, 'add'), 619 count($rem), 620 $this->renderSubscriberList($rem, 'rem')); 621 } else if ($add) { 622 return pht( 623 '%s added %d subscriber(s): %s.', 624 $this->renderHandleLink($author_phid), 625 count($add), 626 $this->renderSubscriberList($add, 'add')); 627 } else if ($rem) { 628 return pht( 629 '%s removed %d subscriber(s): %s.', 630 $this->renderHandleLink($author_phid), 631 count($rem), 632 $this->renderSubscriberList($rem, 'rem')); 633 } else { 634 // This is used when rendering previews, before the user actually 635 // selects any CCs. 636 return pht( 637 '%s updated subscribers...', 638 $this->renderHandleLink($author_phid)); 639 } 640 break; 641 case PhabricatorTransactions::TYPE_EDGE: 642 $new = ipull($new, 'dst'); 643 $old = ipull($old, 'dst'); 644 $add = array_diff($new, $old); 645 $rem = array_diff($old, $new); 646 $type = $this->getMetadata('edge:type'); 647 $type = head($type); 648 649 $type_obj = PhabricatorEdgeType::getByConstant($type); 650 651 if ($add && $rem) { 652 return $type_obj->getTransactionEditString( 653 $this->renderHandleLink($author_phid), 654 new PhutilNumber(count($add) + count($rem)), 655 new PhutilNumber(count($add)), 656 $this->renderHandleList($add), 657 new PhutilNumber(count($rem)), 658 $this->renderHandleList($rem)); 659 } else if ($add) { 660 return $type_obj->getTransactionAddString( 661 $this->renderHandleLink($author_phid), 662 new PhutilNumber(count($add)), 663 $this->renderHandleList($add)); 664 } else if ($rem) { 665 return $type_obj->getTransactionRemoveString( 666 $this->renderHandleLink($author_phid), 667 new PhutilNumber(count($rem)), 668 $this->renderHandleList($rem)); 669 } else { 670 return pht( 671 '%s edited edge metadata.', 672 $this->renderHandleLink($author_phid)); 673 } 674 675 case PhabricatorTransactions::TYPE_CUSTOMFIELD: 676 $field = $this->getTransactionCustomField(); 677 if ($field) { 678 return $field->getApplicationTransactionTitle($this); 679 } else { 680 return pht( 681 '%s edited a custom field.', 682 $this->renderHandleLink($author_phid)); 683 } 684 685 case PhabricatorTransactions::TYPE_TOKEN: 686 if ($old && $new) { 687 return pht( 688 '%s updated a token.', 689 $this->renderHandleLink($author_phid)); 690 } else if ($old) { 691 return pht( 692 '%s rescinded a token.', 693 $this->renderHandleLink($author_phid)); 694 } else { 695 return pht( 696 '%s awarded a token.', 697 $this->renderHandleLink($author_phid)); 698 } 699 700 case PhabricatorTransactions::TYPE_BUILDABLE: 701 switch ($this->getNewValue()) { 702 case HarbormasterBuildable::STATUS_BUILDING: 703 return pht( 704 '%s started building %s.', 705 $this->renderHandleLink($author_phid), 706 $this->renderHandleLink( 707 $this->getMetadataValue('harbormaster:buildablePHID'))); 708 case HarbormasterBuildable::STATUS_PASSED: 709 return pht( 710 '%s completed building %s.', 711 $this->renderHandleLink($author_phid), 712 $this->renderHandleLink( 713 $this->getMetadataValue('harbormaster:buildablePHID'))); 714 case HarbormasterBuildable::STATUS_FAILED: 715 return pht( 716 '%s failed to build %s!', 717 $this->renderHandleLink($author_phid), 718 $this->renderHandleLink( 719 $this->getMetadataValue('harbormaster:buildablePHID'))); 720 default: 721 return null; 722 } 723 724 default: 725 return pht( 726 '%s edited this %s.', 727 $this->renderHandleLink($author_phid), 728 $this->getApplicationObjectTypeName()); 729 } 730 } 731 732 public function getTitleForFeed(PhabricatorFeedStory $story) { 733 $author_phid = $this->getAuthorPHID(); 734 $object_phid = $this->getObjectPHID(); 735 736 $old = $this->getOldValue(); 737 $new = $this->getNewValue(); 738 739 switch ($this->getTransactionType()) { 740 case PhabricatorTransactions::TYPE_COMMENT: 741 return pht( 742 '%s added a comment to %s.', 743 $this->renderHandleLink($author_phid), 744 $this->renderHandleLink($object_phid)); 745 case PhabricatorTransactions::TYPE_VIEW_POLICY: 746 return pht( 747 '%s changed the visibility for %s.', 748 $this->renderHandleLink($author_phid), 749 $this->renderHandleLink($object_phid)); 750 case PhabricatorTransactions::TYPE_EDIT_POLICY: 751 return pht( 752 '%s changed the edit policy for %s.', 753 $this->renderHandleLink($author_phid), 754 $this->renderHandleLink($object_phid)); 755 case PhabricatorTransactions::TYPE_JOIN_POLICY: 756 return pht( 757 '%s changed the join policy for %s.', 758 $this->renderHandleLink($author_phid), 759 $this->renderHandleLink($object_phid)); 760 case PhabricatorTransactions::TYPE_SUBSCRIBERS: 761 return pht( 762 '%s updated subscribers of %s.', 763 $this->renderHandleLink($author_phid), 764 $this->renderHandleLink($object_phid)); 765 case PhabricatorTransactions::TYPE_EDGE: 766 $new = ipull($new, 'dst'); 767 $old = ipull($old, 'dst'); 768 $add = array_diff($new, $old); 769 $rem = array_diff($old, $new); 770 $type = $this->getMetadata('edge:type'); 771 $type = head($type); 772 773 $type_obj = PhabricatorEdgeType::getByConstant($type); 774 775 if ($add && $rem) { 776 return $type_obj->getFeedEditString( 777 $this->renderHandleLink($author_phid), 778 $this->renderHandleLink($object_phid), 779 new PhutilNumber(count($add) + count($rem)), 780 new PhutilNumber(count($add)), 781 $this->renderHandleList($add), 782 new PhutilNumber(count($rem)), 783 $this->renderHandleList($rem)); 784 } else if ($add) { 785 return $type_obj->getFeedAddString( 786 $this->renderHandleLink($author_phid), 787 $this->renderHandleLink($object_phid), 788 new PhutilNumber(count($add)), 789 $this->renderHandleList($add)); 790 } else if ($rem) { 791 return $type_obj->getFeedRemoveString( 792 $this->renderHandleLink($author_phid), 793 $this->renderHandleLink($object_phid), 794 new PhutilNumber(count($rem)), 795 $this->renderHandleList($rem)); 796 } else { 797 return pht( 798 '%s edited edge metadata for %s.', 799 $this->renderHandleLink($author_phid), 800 $this->renderHandleLink($object_phid)); 801 } 802 803 case PhabricatorTransactions::TYPE_CUSTOMFIELD: 804 $field = $this->getTransactionCustomField(); 805 if ($field) { 806 return $field->getApplicationTransactionTitleForFeed($this, $story); 807 } else { 808 return pht( 809 '%s edited a custom field on %s.', 810 $this->renderHandleLink($author_phid), 811 $this->renderHandleLink($object_phid)); 812 } 813 case PhabricatorTransactions::TYPE_BUILDABLE: 814 switch ($this->getNewValue()) { 815 case HarbormasterBuildable::STATUS_BUILDING: 816 return pht( 817 '%s started building %s for %s.', 818 $this->renderHandleLink($author_phid), 819 $this->renderHandleLink( 820 $this->getMetadataValue('harbormaster:buildablePHID')), 821 $this->renderHandleLink($object_phid)); 822 case HarbormasterBuildable::STATUS_PASSED: 823 return pht( 824 '%s completed building %s for %s.', 825 $this->renderHandleLink($author_phid), 826 $this->renderHandleLink( 827 $this->getMetadataValue('harbormaster:buildablePHID')), 828 $this->renderHandleLink($object_phid)); 829 case HarbormasterBuildable::STATUS_FAILED: 830 return pht( 831 '%s failed to build %s for %s.', 832 $this->renderHandleLink($author_phid), 833 $this->renderHandleLink( 834 $this->getMetadataValue('harbormaster:buildablePHID')), 835 $this->renderHandleLink($object_phid)); 836 default: 837 return null; 838 } 839 840 } 841 842 return $this->getTitle(); 843 } 844 845 public function getMarkupFieldsForFeed(PhabricatorFeedStory $story) { 846 $fields = array(); 847 848 switch ($this->getTransactionType()) { 849 case PhabricatorTransactions::TYPE_COMMENT: 850 $text = $this->getComment()->getContent(); 851 if (strlen($text)) { 852 $fields[] = 'comment/'.$this->getID(); 853 } 854 break; 855 } 856 857 return $fields; 858 } 859 860 public function getMarkupTextForFeed(PhabricatorFeedStory $story, $field) { 861 switch ($this->getTransactionType()) { 862 case PhabricatorTransactions::TYPE_COMMENT: 863 $text = $this->getComment()->getContent(); 864 return PhabricatorMarkupEngine::summarize($text); 865 } 866 867 return null; 868 } 869 870 public function getBodyForFeed(PhabricatorFeedStory $story) { 871 $old = $this->getOldValue(); 872 $new = $this->getNewValue(); 873 874 $body = null; 875 876 switch ($this->getTransactionType()) { 877 case PhabricatorTransactions::TYPE_COMMENT: 878 $text = $this->getComment()->getContent(); 879 if (strlen($text)) { 880 $body = $story->getMarkupFieldOutput('comment/'.$this->getID()); 881 } 882 break; 883 } 884 885 return $body; 886 } 887 888 public function getActionStrength() { 889 switch ($this->getTransactionType()) { 890 case PhabricatorTransactions::TYPE_COMMENT: 891 return 0.5; 892 case PhabricatorTransactions::TYPE_SUBSCRIBERS: 893 $old = $this->getOldValue(); 894 $new = $this->getNewValue(); 895 896 $add = array_diff($old, $new); 897 $rem = array_diff($new, $old); 898 899 // If this action is the actor subscribing or unsubscribing themselves, 900 // it is less interesting. In particular, if someone makes a comment and 901 // also implicitly subscribes themselves, we should treat the 902 // transaction group as "comment", not "subscribe". In this specific 903 // case (one affected user, and that affected user it the actor), 904 // decrease the action strength. 905 906 if ((count($add) + count($rem)) != 1) { 907 // Not exactly one CC change. 908 break; 909 } 910 911 $affected_phid = head(array_merge($add, $rem)); 912 if ($affected_phid != $this->getAuthorPHID()) { 913 // Affected user is someone else. 914 break; 915 } 916 917 // Make this weaker than TYPE_COMMENT. 918 return 0.25; 919 } 920 return 1.0; 921 } 922 923 public function isCommentTransaction() { 924 if ($this->hasComment()) { 925 return true; 926 } 927 928 switch ($this->getTransactionType()) { 929 case PhabricatorTransactions::TYPE_COMMENT: 930 return true; 931 } 932 933 return false; 934 } 935 936 public function getActionName() { 937 switch ($this->getTransactionType()) { 938 case PhabricatorTransactions::TYPE_COMMENT: 939 return pht('Commented On'); 940 case PhabricatorTransactions::TYPE_VIEW_POLICY: 941 case PhabricatorTransactions::TYPE_EDIT_POLICY: 942 case PhabricatorTransactions::TYPE_JOIN_POLICY: 943 return pht('Changed Policy'); 944 case PhabricatorTransactions::TYPE_SUBSCRIBERS: 945 return pht('Changed Subscribers'); 946 case PhabricatorTransactions::TYPE_BUILDABLE: 947 switch ($this->getNewValue()) { 948 case HarbormasterBuildable::STATUS_PASSED: 949 return pht('Build Passed'); 950 case HarbormasterBuildable::STATUS_FAILED: 951 return pht('Build Failed'); 952 default: 953 return pht('Build Status'); 954 } 955 default: 956 return pht('Updated'); 957 } 958 } 959 960 public function getMailTags() { 961 return array(); 962 } 963 964 public function hasChangeDetails() { 965 switch ($this->getTransactionType()) { 966 case PhabricatorTransactions::TYPE_CUSTOMFIELD: 967 $field = $this->getTransactionCustomField(); 968 if ($field) { 969 return $field->getApplicationTransactionHasChangeDetails($this); 970 } 971 break; 972 } 973 return false; 974 } 975 976 public function renderChangeDetails(PhabricatorUser $viewer) { 977 switch ($this->getTransactionType()) { 978 case PhabricatorTransactions::TYPE_CUSTOMFIELD: 979 $field = $this->getTransactionCustomField(); 980 if ($field) { 981 return $field->getApplicationTransactionChangeDetails($this, $viewer); 982 } 983 break; 984 } 985 986 return $this->renderTextCorpusChangeDetails( 987 $viewer, 988 $this->getOldValue(), 989 $this->getNewValue()); 990 } 991 992 public function renderTextCorpusChangeDetails( 993 PhabricatorUser $viewer, 994 $old, 995 $new) { 996 997 require_celerity_resource('differential-changeset-view-css'); 998 999 $view = id(new PhabricatorApplicationTransactionTextDiffDetailView()) 1000 ->setUser($viewer) 1001 ->setOldText($old) 1002 ->setNewText($new); 1003 1004 return $view->render(); 1005 } 1006 1007 public function attachTransactionGroup(array $group) { 1008 assert_instances_of($group, 'PhabricatorApplicationTransaction'); 1009 $this->transactionGroup = $group; 1010 return $this; 1011 } 1012 1013 public function getTransactionGroup() { 1014 return $this->transactionGroup; 1015 } 1016 1017 /** 1018 * Should this transaction be visually grouped with an existing transaction 1019 * group? 1020 * 1021 * @param list<PhabricatorApplicationTransaction> List of transactions. 1022 * @return bool True to display in a group with the other transactions. 1023 */ 1024 public function shouldDisplayGroupWith(array $group) { 1025 $this_source = null; 1026 if ($this->getContentSource()) { 1027 $this_source = $this->getContentSource()->getSource(); 1028 } 1029 1030 foreach ($group as $xaction) { 1031 // Don't group transactions by different authors. 1032 if ($xaction->getAuthorPHID() != $this->getAuthorPHID()) { 1033 return false; 1034 } 1035 1036 // Don't group transactions for different objects. 1037 if ($xaction->getObjectPHID() != $this->getObjectPHID()) { 1038 return false; 1039 } 1040 1041 // Don't group anything into a group which already has a comment. 1042 if ($xaction->isCommentTransaction()) { 1043 return false; 1044 } 1045 1046 // Don't group transactions from different content sources. 1047 $other_source = null; 1048 if ($xaction->getContentSource()) { 1049 $other_source = $xaction->getContentSource()->getSource(); 1050 } 1051 1052 if ($other_source != $this_source) { 1053 return false; 1054 } 1055 1056 // Don't group transactions which happened more than 2 minutes apart. 1057 $apart = abs($xaction->getDateCreated() - $this->getDateCreated()); 1058 if ($apart > (60 * 2)) { 1059 return false; 1060 } 1061 } 1062 1063 return true; 1064 } 1065 1066 public function renderExtraInformationLink() { 1067 $herald_xscript_id = $this->getMetadataValue('herald:transcriptID'); 1068 1069 if ($herald_xscript_id) { 1070 return phutil_tag( 1071 'a', 1072 array( 1073 'href' => '/herald/transcript/'.$herald_xscript_id.'/', 1074 ), 1075 pht('View Herald Transcript')); 1076 } 1077 1078 return null; 1079 } 1080 1081 public function renderAsTextForDoorkeeper( 1082 DoorkeeperFeedStoryPublisher $publisher, 1083 PhabricatorFeedStory $story, 1084 array $xactions) { 1085 1086 $text = array(); 1087 $body = array(); 1088 1089 foreach ($xactions as $xaction) { 1090 $xaction_body = $xaction->getBodyForMail(); 1091 if ($xaction_body !== null) { 1092 $body[] = $xaction_body; 1093 } 1094 1095 if ($xaction->shouldHideForMail($xactions)) { 1096 continue; 1097 } 1098 1099 $old_target = $xaction->getRenderingTarget(); 1100 $new_target = PhabricatorApplicationTransaction::TARGET_TEXT; 1101 $xaction->setRenderingTarget($new_target); 1102 1103 if ($publisher->getRenderWithImpliedContext()) { 1104 $text[] = $xaction->getTitle(); 1105 } else { 1106 $text[] = $xaction->getTitleForFeed($story); 1107 } 1108 1109 $xaction->setRenderingTarget($old_target); 1110 } 1111 1112 $text = implode("\n", $text); 1113 $body = implode("\n\n", $body); 1114 1115 return rtrim($text."\n\n".$body); 1116 } 1117 1118 1119 1120 /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ 1121 1122 1123 public function getCapabilities() { 1124 return array( 1125 PhabricatorPolicyCapability::CAN_VIEW, 1126 PhabricatorPolicyCapability::CAN_EDIT, 1127 ); 1128 } 1129 1130 public function getPolicy($capability) { 1131 switch ($capability) { 1132 case PhabricatorPolicyCapability::CAN_VIEW: 1133 return $this->getViewPolicy(); 1134 case PhabricatorPolicyCapability::CAN_EDIT: 1135 return $this->getEditPolicy(); 1136 } 1137 } 1138 1139 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 1140 return ($viewer->getPHID() == $this->getAuthorPHID()); 1141 } 1142 1143 public function describeAutomaticCapability($capability) { 1144 // TODO: (T603) Exact policies are unclear here. 1145 return null; 1146 } 1147 1148 1149 /* -( PhabricatorDestructibleInterface )----------------------------------- */ 1150 1151 1152 public function destroyObjectPermanently( 1153 PhabricatorDestructionEngine $engine) { 1154 1155 $this->openTransaction(); 1156 $comment_template = null; 1157 try { 1158 $comment_template = $this->getApplicationTransactionCommentObject(); 1159 } catch (Exception $ex) { 1160 // Continue; no comments for these transactions. 1161 } 1162 1163 if ($comment_template) { 1164 $comments = $comment_template->loadAllWhere( 1165 'transactionPHID = %s', 1166 $this->getPHID()); 1167 foreach ($comments as $comment) { 1168 $engine->destroyObject($comment); 1169 } 1170 } 1171 1172 $this->delete(); 1173 $this->saveTransaction(); 1174 } 1175 1176 1177 }
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 |