[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/transactions/storage/ -> PhabricatorApplicationTransaction.php (source)

   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  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1