[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/audit/editor/ -> PhabricatorAuditEditor.php (source)

   1  <?php
   2  
   3  final class PhabricatorAuditEditor
   4    extends PhabricatorApplicationTransactionEditor {
   5  
   6    const MAX_FILES_SHOWN_IN_EMAIL = 1000;
   7  
   8    private $auditReasonMap = array();
   9    private $heraldEmailPHIDs = array();
  10    private $affectedFiles;
  11    private $rawPatch;
  12  
  13    public function addAuditReason($phid, $reason) {
  14      if (!isset($this->auditReasonMap[$phid])) {
  15        $this->auditReasonMap[$phid] = array();
  16      }
  17      $this->auditReasonMap[$phid][] = $reason;
  18      return $this;
  19    }
  20  
  21    private function getAuditReasons($phid) {
  22      if (isset($this->auditReasonMap[$phid])) {
  23        return $this->auditReasonMap[$phid];
  24      }
  25      if ($this->getIsHeraldEditor()) {
  26        $name = 'herald';
  27      } else {
  28        $name = $this->getActor()->getUsername();
  29      }
  30      return array('Added by '.$name.'.');
  31    }
  32  
  33    public function setRawPatch($patch) {
  34      $this->rawPatch = $patch;
  35      return $this;
  36    }
  37  
  38    public function getRawPatch() {
  39      return $this->rawPatch;
  40    }
  41  
  42    public function getEditorApplicationClass() {
  43      return 'PhabricatorAuditApplication';
  44    }
  45  
  46    public function getEditorObjectsDescription() {
  47      return pht('Audits');
  48    }
  49  
  50    public function getTransactionTypes() {
  51      $types = parent::getTransactionTypes();
  52  
  53      $types[] = PhabricatorTransactions::TYPE_COMMENT;
  54      $types[] = PhabricatorTransactions::TYPE_EDGE;
  55  
  56      $types[] = PhabricatorAuditTransaction::TYPE_COMMIT;
  57  
  58      // TODO: These will get modernized eventually, but that can happen one
  59      // at a time later on.
  60      $types[] = PhabricatorAuditActionConstants::ACTION;
  61      $types[] = PhabricatorAuditActionConstants::INLINE;
  62      $types[] = PhabricatorAuditActionConstants::ADD_AUDITORS;
  63  
  64      return $types;
  65    }
  66  
  67    protected function transactionHasEffect(
  68      PhabricatorLiskDAO $object,
  69      PhabricatorApplicationTransaction $xaction) {
  70  
  71      switch ($xaction->getTransactionType()) {
  72        case PhabricatorAuditActionConstants::INLINE:
  73          return $xaction->hasComment();
  74      }
  75  
  76      return parent::transactionHasEffect($object, $xaction);
  77    }
  78  
  79    protected function getCustomTransactionOldValue(
  80      PhabricatorLiskDAO $object,
  81      PhabricatorApplicationTransaction $xaction) {
  82      switch ($xaction->getTransactionType()) {
  83        case PhabricatorAuditActionConstants::ACTION:
  84        case PhabricatorAuditActionConstants::INLINE:
  85        case PhabricatorAuditTransaction::TYPE_COMMIT:
  86          return null;
  87        case PhabricatorAuditActionConstants::ADD_AUDITORS:
  88          // TODO: For now, just record the added PHIDs. Eventually, turn these
  89          // into real edge transactions, probably?
  90          return array();
  91      }
  92  
  93      return parent::getCustomTransactionOldValue($object, $xaction);
  94    }
  95  
  96    protected function getCustomTransactionNewValue(
  97      PhabricatorLiskDAO $object,
  98      PhabricatorApplicationTransaction $xaction) {
  99  
 100      switch ($xaction->getTransactionType()) {
 101        case PhabricatorAuditActionConstants::ACTION:
 102        case PhabricatorAuditActionConstants::INLINE:
 103        case PhabricatorAuditActionConstants::ADD_AUDITORS:
 104        case PhabricatorAuditTransaction::TYPE_COMMIT:
 105          return $xaction->getNewValue();
 106      }
 107  
 108      return parent::getCustomTransactionNewValue($object, $xaction);
 109    }
 110  
 111    protected function applyCustomInternalTransaction(
 112      PhabricatorLiskDAO $object,
 113      PhabricatorApplicationTransaction $xaction) {
 114  
 115      switch ($xaction->getTransactionType()) {
 116        case PhabricatorTransactions::TYPE_COMMENT:
 117        case PhabricatorTransactions::TYPE_SUBSCRIBERS:
 118        case PhabricatorTransactions::TYPE_EDGE:
 119        case PhabricatorAuditActionConstants::ACTION:
 120        case PhabricatorAuditActionConstants::INLINE:
 121        case PhabricatorAuditActionConstants::ADD_AUDITORS:
 122        case PhabricatorAuditTransaction::TYPE_COMMIT:
 123          return;
 124      }
 125  
 126      return parent::applyCustomInternalTransaction($object, $xaction);
 127    }
 128  
 129    protected function applyCustomExternalTransaction(
 130      PhabricatorLiskDAO $object,
 131      PhabricatorApplicationTransaction $xaction) {
 132  
 133      switch ($xaction->getTransactionType()) {
 134        case PhabricatorTransactions::TYPE_COMMENT:
 135        case PhabricatorTransactions::TYPE_SUBSCRIBERS:
 136        case PhabricatorTransactions::TYPE_EDGE:
 137        case PhabricatorAuditActionConstants::ACTION:
 138        case PhabricatorAuditActionConstants::INLINE:
 139        case PhabricatorAuditTransaction::TYPE_COMMIT:
 140          return;
 141        case PhabricatorAuditActionConstants::ADD_AUDITORS:
 142          $new = $xaction->getNewValue();
 143          if (!is_array($new)) {
 144            $new = array();
 145          }
 146  
 147          $old = $xaction->getOldValue();
 148          if (!is_array($old)) {
 149            $old = array();
 150          }
 151  
 152          $add = array_diff_key($new, $old);
 153  
 154          $actor = $this->requireActor();
 155  
 156          $requests = $object->getAudits();
 157          $requests = mpull($requests, null, 'getAuditorPHID');
 158          foreach ($add as $phid) {
 159            if (isset($requests[$phid])) {
 160              continue;
 161            }
 162  
 163            if ($this->getIsHeraldEditor()) {
 164              $audit_requested = $xaction->getMetadataValue('auditStatus');
 165              $audit_reason_map = $xaction->getMetadataValue('auditReasonMap');
 166              $audit_reason = $audit_reason_map[$phid];
 167            } else {
 168              $audit_requested = PhabricatorAuditStatusConstants::AUDIT_REQUESTED;
 169              $audit_reason = $this->getAuditReasons($phid);
 170            }
 171            $requests[] = id (new PhabricatorRepositoryAuditRequest())
 172              ->setCommitPHID($object->getPHID())
 173              ->setAuditorPHID($phid)
 174              ->setAuditStatus($audit_requested)
 175              ->setAuditReasons($audit_reason)
 176              ->save();
 177          }
 178  
 179          $object->attachAudits($requests);
 180          return;
 181      }
 182  
 183      return parent::applyCustomExternalTransaction($object, $xaction);
 184    }
 185  
 186    protected function applyFinalEffects(
 187      PhabricatorLiskDAO $object,
 188      array $xactions) {
 189  
 190      // Load auditors explicitly; we may not have them if the caller was a
 191      // generic piece of infrastructure.
 192  
 193      $commit = id(new DiffusionCommitQuery())
 194        ->setViewer($this->requireActor())
 195        ->withIDs(array($object->getID()))
 196        ->needAuditRequests(true)
 197        ->executeOne();
 198      if (!$commit) {
 199        throw new Exception(
 200          pht('Failed to load commit during transaction finalization!'));
 201      }
 202      $object->attachAudits($commit->getAudits());
 203  
 204      $status_concerned = PhabricatorAuditStatusConstants::CONCERNED;
 205      $status_closed = PhabricatorAuditStatusConstants::CLOSED;
 206      $status_resigned = PhabricatorAuditStatusConstants::RESIGNED;
 207      $status_accepted = PhabricatorAuditStatusConstants::ACCEPTED;
 208      $status_concerned = PhabricatorAuditStatusConstants::CONCERNED;
 209  
 210      $actor_phid = $this->getActingAsPHID();
 211      $actor_is_author = ($object->getAuthorPHID()) &&
 212        ($actor_phid == $object->getAuthorPHID());
 213  
 214      $import_status_flag = null;
 215      foreach ($xactions as $xaction) {
 216        switch ($xaction->getTransactionType()) {
 217          case PhabricatorAuditTransaction::TYPE_COMMIT:
 218            $import_status_flag = PhabricatorRepositoryCommit::IMPORTED_HERALD;
 219            break;
 220          case PhabricatorAuditActionConstants::ACTION:
 221            $new = $xaction->getNewValue();
 222            switch ($new) {
 223              case PhabricatorAuditActionConstants::CLOSE:
 224                // "Close" means wipe out all the concerns.
 225                $requests = $object->getAudits();
 226                foreach ($requests as $request) {
 227                  if ($request->getAuditStatus() == $status_concerned) {
 228                    $request
 229                      ->setAuditStatus($status_closed)
 230                      ->save();
 231                  }
 232                }
 233                break;
 234              case PhabricatorAuditActionConstants::RESIGN:
 235                $requests = $object->getAudits();
 236                $requests = mpull($requests, null, 'getAuditorPHID');
 237                $actor_request = idx($requests, $actor_phid);
 238  
 239                // If the actor doesn't currently have a relationship to the
 240                // commit, add one explicitly. For example, this allows members
 241                // of a project to resign from a commit and have it drop out of
 242                // their queue.
 243  
 244                if (!$actor_request) {
 245                  $actor_request = id(new PhabricatorRepositoryAuditRequest())
 246                    ->setCommitPHID($object->getPHID())
 247                    ->setAuditorPHID($actor_phid);
 248  
 249                  $requests[] = $actor_request;
 250                  $object->attachAudits($requests);
 251                }
 252  
 253                $actor_request
 254                  ->setAuditStatus($status_resigned)
 255                  ->save();
 256                break;
 257              case PhabricatorAuditActionConstants::ACCEPT:
 258              case PhabricatorAuditActionConstants::CONCERN:
 259                if ($new == PhabricatorAuditActionConstants::ACCEPT) {
 260                  $new_status = $status_accepted;
 261                } else {
 262                  $new_status = $status_concerned;
 263                }
 264  
 265                $requests = $object->getAudits();
 266                $requests = mpull($requests, null, 'getAuditorPHID');
 267  
 268                // Figure out which requests the actor has authority over: these
 269                // are user requests where they are the auditor, and packages
 270                // and projects they are a member of.
 271  
 272                if ($actor_is_author) {
 273                  // When modifying your own commits, you act only on behalf of
 274                  // yourself, not your packages/projects -- the idea being that
 275                  // you can't accept your own commits.
 276                  $authority_phids = array($actor_phid);
 277                } else {
 278                  $authority_phids =
 279                    PhabricatorAuditCommentEditor::loadAuditPHIDsForUser(
 280                      $this->requireActor());
 281                }
 282  
 283                $authority = array_select_keys(
 284                  $requests,
 285                  $authority_phids);
 286  
 287                if (!$authority) {
 288                  // If the actor has no authority over any existing requests,
 289                  // create a new request for them.
 290  
 291                  $actor_request = id(new PhabricatorRepositoryAuditRequest())
 292                    ->setCommitPHID($object->getPHID())
 293                    ->setAuditorPHID($actor_phid)
 294                    ->setAuditStatus($new_status)
 295                    ->save();
 296  
 297                  $requests[$actor_phid] = $actor_request;
 298                  $object->attachAudits($requests);
 299                } else {
 300                  // Otherwise, update the audit status of the existing requests.
 301                  foreach ($authority as $request) {
 302                    $request
 303                      ->setAuditStatus($new_status)
 304                      ->save();
 305                  }
 306                }
 307                break;
 308  
 309            }
 310            break;
 311        }
 312      }
 313  
 314      $requests = $object->getAudits();
 315      $object->updateAuditStatus($requests);
 316      $object->save();
 317  
 318      if ($import_status_flag) {
 319        $object->writeImportStatusFlag($import_status_flag);
 320      }
 321  
 322      return $xactions;
 323    }
 324  
 325    protected function expandTransaction(
 326      PhabricatorLiskDAO $object,
 327      PhabricatorApplicationTransaction $xaction) {
 328  
 329      $xactions = parent::expandTransaction($object, $xaction);
 330      switch ($xaction->getTransactionType()) {
 331        case PhabricatorAuditTransaction::TYPE_COMMIT:
 332          $request = $this->createAuditRequestTransactionFromCommitMessage(
 333            $object);
 334          if ($request) {
 335            $xactions[] = $request;
 336            $this->setUnmentionablePHIDMap($request->getNewValue());
 337          }
 338          break;
 339        default:
 340          break;
 341      }
 342      return $xactions;
 343    }
 344  
 345    private function createAuditRequestTransactionFromCommitMessage(
 346      PhabricatorRepositoryCommit $commit) {
 347  
 348      $data = $commit->getCommitData();
 349      $message = $data->getCommitMessage();
 350  
 351      $matches = null;
 352      if (!preg_match('/^Auditors:\s*(.*)$/im', $message, $matches)) {
 353        return array();
 354      }
 355  
 356      $phids = id(new PhabricatorObjectListQuery())
 357        ->setViewer($this->getActor())
 358        ->setAllowPartialResults(true)
 359        ->setAllowedTypes(
 360          array(
 361            PhabricatorPeopleUserPHIDType::TYPECONST,
 362            PhabricatorProjectProjectPHIDType::TYPECONST,
 363          ))
 364        ->setObjectList($matches[1])
 365        ->execute();
 366  
 367      if (!$phids) {
 368        return array();
 369      }
 370  
 371      foreach ($phids as $phid) {
 372        $this->addAuditReason($phid, 'Requested by Author');
 373      }
 374      return id(new PhabricatorAuditTransaction())
 375        ->setTransactionType(PhabricatorAuditActionConstants::ADD_AUDITORS)
 376        ->setNewValue(array_fuse($phids));
 377    }
 378  
 379    protected function sortTransactions(array $xactions) {
 380      $xactions = parent::sortTransactions($xactions);
 381  
 382      $head = array();
 383      $tail = array();
 384  
 385      foreach ($xactions as $xaction) {
 386        $type = $xaction->getTransactionType();
 387        if ($type == PhabricatorAuditActionConstants::INLINE) {
 388          $tail[] = $xaction;
 389        } else {
 390          $head[] = $xaction;
 391        }
 392      }
 393  
 394      return array_values(array_merge($head, $tail));
 395    }
 396  
 397    protected function validateTransaction(
 398      PhabricatorLiskDAO $object,
 399      $type,
 400      array $xactions) {
 401  
 402      $errors = parent::validateTransaction($object, $type, $xactions);
 403  
 404      foreach ($xactions as $xaction) {
 405        switch ($type) {
 406          case PhabricatorAuditActionConstants::ACTION:
 407            $error = $this->validateAuditAction(
 408              $object,
 409              $type,
 410              $xaction,
 411              $xaction->getNewValue());
 412            if ($error) {
 413              $errors[] = new PhabricatorApplicationTransactionValidationError(
 414                $type,
 415                pht('Invalid'),
 416                $error,
 417                $xaction);
 418            }
 419            break;
 420        }
 421      }
 422  
 423      return $errors;
 424    }
 425  
 426    private function validateAuditAction(
 427      PhabricatorLiskDAO $object,
 428      $type,
 429      PhabricatorAuditTransaction $xaction,
 430      $action) {
 431  
 432      $can_author_close_key = 'audit.can-author-close-audit';
 433      $can_author_close = PhabricatorEnv::getEnvConfig($can_author_close_key);
 434  
 435      $actor_is_author = ($object->getAuthorPHID()) &&
 436        ($object->getAuthorPHID() == $this->getActingAsPHID());
 437  
 438      switch ($action) {
 439        case PhabricatorAuditActionConstants::CLOSE:
 440          if (!$actor_is_author) {
 441            return pht(
 442              'You can not close this audit because you are not the author '.
 443              'of the commit.');
 444          }
 445  
 446          if (!$can_author_close) {
 447            return pht(
 448              'You can not close this audit because "%s" is disabled in '.
 449              'the Phabricator configuration.',
 450              $can_author_close_key);
 451          }
 452  
 453          break;
 454      }
 455  
 456      return null;
 457    }
 458  
 459  
 460    protected function supportsSearch() {
 461      return true;
 462    }
 463  
 464    protected function expandCustomRemarkupBlockTransactions(
 465      PhabricatorLiskDAO $object,
 466      array $xactions,
 467      $blocks,
 468      PhutilMarkupEngine $engine) {
 469  
 470      // we are only really trying to find unmentionable phids here...
 471      // don't bother with this outside initial commit (i.e. create)
 472      // transaction
 473      $is_commit = false;
 474      foreach ($xactions as $xaction) {
 475        switch ($xaction->getTransactionType()) {
 476          case PhabricatorAuditTransaction::TYPE_COMMIT:
 477            $is_commit = true;
 478            break;
 479        }
 480      }
 481  
 482      // "result" is always an array....
 483      $result = array();
 484      if (!$is_commit) {
 485        return $result;
 486      }
 487  
 488      $flat_blocks = array_mergev($blocks);
 489      $huge_block = implode("\n\n", $flat_blocks);
 490      $phid_map = array();
 491      $phid_map[] = $this->getUnmentionablePHIDMap();
 492      $monograms = array();
 493  
 494      $task_refs = id(new ManiphestCustomFieldStatusParser())
 495        ->parseCorpus($huge_block);
 496      foreach ($task_refs as $match) {
 497        foreach ($match['monograms'] as $monogram) {
 498          $monograms[] = $monogram;
 499        }
 500      }
 501  
 502      $rev_refs = id(new DifferentialCustomFieldDependsOnParser())
 503        ->parseCorpus($huge_block);
 504      foreach ($rev_refs as $match) {
 505        foreach ($match['monograms'] as $monogram) {
 506          $monograms[] = $monogram;
 507        }
 508      }
 509  
 510      $objects = id(new PhabricatorObjectQuery())
 511        ->setViewer($this->getActor())
 512        ->withNames($monograms)
 513        ->execute();
 514      $phid_map[] = mpull($objects, 'getPHID', 'getPHID');
 515      $phid_map = array_mergev($phid_map);
 516      $this->setUnmentionablePHIDMap($phid_map);
 517  
 518      return $result;
 519    }
 520  
 521  
 522    protected function shouldSendMail(
 523      PhabricatorLiskDAO $object,
 524      array $xactions) {
 525  
 526      // not every code path loads the repository so tread carefully
 527      if ($object->getRepository($assert_attached = false)) {
 528        $repository = $object->getRepository();
 529        if ($repository->isImporting()) {
 530          return false;
 531        }
 532      }
 533      return $this->isCommitMostlyImported($object);
 534    }
 535  
 536    protected function buildReplyHandler(PhabricatorLiskDAO $object) {
 537      $reply_handler = PhabricatorEnv::newObjectFromConfig(
 538        'metamta.diffusion.reply-handler');
 539      $reply_handler->setMailReceiver($object);
 540      return $reply_handler;
 541    }
 542  
 543    protected function getMailSubjectPrefix() {
 544      return PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix');
 545    }
 546  
 547    protected function getMailThreadID(PhabricatorLiskDAO $object) {
 548      // For backward compatibility, use this legacy thread ID.
 549      return 'diffusion-audit-'.$object->getPHID();
 550    }
 551  
 552    protected function buildMailTemplate(PhabricatorLiskDAO $object) {
 553      $identifier = $object->getCommitIdentifier();
 554      $repository = $object->getRepository();
 555      $monogram = $repository->getMonogram();
 556  
 557      $summary = $object->getSummary();
 558      $name = $repository->formatCommitName($identifier);
 559  
 560      $subject = "{$name}: {$summary}";
 561      $thread_topic = "Commit {$monogram}{$identifier}";
 562  
 563      $template = id(new PhabricatorMetaMTAMail())
 564        ->setSubject($subject)
 565        ->addHeader('Thread-Topic', $thread_topic);
 566  
 567      $this->attachPatch(
 568        $template,
 569        $object);
 570  
 571      return $template;
 572    }
 573  
 574    protected function getMailTo(PhabricatorLiskDAO $object) {
 575      $phids = array();
 576      if ($this->heraldEmailPHIDs) {
 577        $phids = $this->heraldEmailPHIDs;
 578      }
 579  
 580      if ($object->getAuthorPHID()) {
 581        $phids[] = $object->getAuthorPHID();
 582      }
 583  
 584      $status_resigned = PhabricatorAuditStatusConstants::RESIGNED;
 585      foreach ($object->getAudits() as $audit) {
 586        if ($audit->getAuditStatus() != $status_resigned) {
 587          $phids[] = $audit->getAuditorPHID();
 588        }
 589      }
 590  
 591      return $phids;
 592    }
 593  
 594    protected function buildMailBody(
 595      PhabricatorLiskDAO $object,
 596      array $xactions) {
 597  
 598      $body = parent::buildMailBody($object, $xactions);
 599  
 600      $type_inline = PhabricatorAuditActionConstants::INLINE;
 601      $type_push = PhabricatorAuditTransaction::TYPE_COMMIT;
 602  
 603      $is_commit = false;
 604      $inlines = array();
 605      foreach ($xactions as $xaction) {
 606        if ($xaction->getTransactionType() == $type_inline) {
 607          $inlines[] = $xaction;
 608        }
 609        if ($xaction->getTransactionType() == $type_push) {
 610          $is_commit = true;
 611        }
 612      }
 613  
 614      if ($inlines) {
 615        $body->addTextSection(
 616          pht('INLINE COMMENTS'),
 617          $this->renderInlineCommentsForMail($object, $inlines));
 618      }
 619  
 620      if ($is_commit) {
 621        $data = $object->getCommitData();
 622        $body->addTextSection(pht('AFFECTED FILES'), $this->affectedFiles);
 623        $this->inlinePatch(
 624          $body,
 625          $object);
 626      }
 627  
 628      // Reload the commit to pull commit data.
 629      $commit = id(new DiffusionCommitQuery())
 630        ->setViewer($this->requireActor())
 631        ->withIDs(array($object->getID()))
 632        ->needCommitData(true)
 633        ->executeOne();
 634      $data = $commit->getCommitData();
 635  
 636      $user_phids = array();
 637  
 638      $author_phid = $commit->getAuthorPHID();
 639      if ($author_phid) {
 640        $user_phids[$author_phid][] = pht('Author');
 641      }
 642  
 643      $committer_phid = $data->getCommitDetail('committerPHID');
 644      if ($committer_phid && ($committer_phid != $author_phid)) {
 645        $user_phids[$committer_phid][] = pht('Committer');
 646      }
 647  
 648      // we loaded this in applyFinalEffects
 649      $audit_requests = $object->getAudits();
 650      $auditor_phids = mpull($audit_requests, 'getAuditorPHID');
 651      foreach ($auditor_phids as $auditor_phid) {
 652        $user_phids[$auditor_phid][] = pht('Auditor');
 653      }
 654  
 655      // TODO: It would be nice to show pusher here too, but that information
 656      // is a little tricky to get at right now.
 657  
 658      if ($user_phids) {
 659        $handle_phids = array_keys($user_phids);
 660        $handles = id(new PhabricatorHandleQuery())
 661          ->setViewer($this->requireActor())
 662          ->withPHIDs($handle_phids)
 663          ->execute();
 664  
 665        $user_info = array();
 666        foreach ($user_phids as $phid => $roles) {
 667          $user_info[] = pht(
 668            '%s (%s)',
 669            $handles[$phid]->getName(),
 670            implode(', ', $roles));
 671        }
 672  
 673        $body->addTextSection(
 674          pht('USERS'),
 675          implode("\n", $user_info));
 676      }
 677  
 678      $monogram = $object->getRepository()->formatCommitName(
 679        $object->getCommitIdentifier());
 680  
 681      $body->addLinkSection(
 682        pht('COMMIT'),
 683        PhabricatorEnv::getProductionURI('/'.$monogram));
 684  
 685      return $body;
 686    }
 687  
 688    private function attachPatch(
 689      PhabricatorMetaMTAMail $template,
 690      PhabricatorRepositoryCommit $commit) {
 691  
 692      if (!$this->getRawPatch()) {
 693        return;
 694      }
 695  
 696      $attach_key = 'metamta.diffusion.attach-patches';
 697      $attach_patches = PhabricatorEnv::getEnvConfig($attach_key);
 698      if (!$attach_patches) {
 699        return;
 700      }
 701  
 702      $repository = $commit->getRepository();
 703      $encoding = $repository->getDetail('encoding', 'UTF-8');
 704  
 705      $raw_patch = $this->getRawPatch();
 706      $commit_name = $repository->formatCommitName(
 707        $commit->getCommitIdentifier());
 708  
 709      $template->addAttachment(
 710        new PhabricatorMetaMTAAttachment(
 711          $raw_patch,
 712          $commit_name.'.patch',
 713          'text/x-patch; charset='.$encoding));
 714    }
 715  
 716    private function inlinePatch(
 717      PhabricatorMetaMTAMailBody $body,
 718      PhabricatorRepositoryCommit $commit) {
 719  
 720      if (!$this->getRawPatch()) {
 721          return;
 722      }
 723  
 724      $inline_key = 'metamta.diffusion.inline-patches';
 725      $inline_patches = PhabricatorEnv::getEnvConfig($inline_key);
 726      if (!$inline_patches) {
 727        return;
 728      }
 729  
 730      $repository = $commit->getRepository();
 731      $raw_patch = $this->getRawPatch();
 732      $result = null;
 733      $len = substr_count($raw_patch, "\n");
 734      if ($len <= $inline_patches) {
 735        // We send email as utf8, so we need to convert the text to utf8 if
 736        // we can.
 737        $encoding = $repository->getDetail('encoding', 'UTF-8');
 738        if ($encoding) {
 739          $raw_patch = phutil_utf8_convert($raw_patch, 'UTF-8', $encoding);
 740        }
 741        $result = phutil_utf8ize($raw_patch);
 742      }
 743  
 744      if ($result) {
 745        $result = "PATCH\n\n{$result}\n";
 746      }
 747      $body->addRawSection($result);
 748    }
 749  
 750    private function renderInlineCommentsForMail(
 751      PhabricatorLiskDAO $object,
 752      array $inline_xactions) {
 753  
 754      $inlines = mpull($inline_xactions, 'getComment');
 755  
 756      $block = array();
 757  
 758      $path_map = id(new DiffusionPathQuery())
 759        ->withPathIDs(mpull($inlines, 'getPathID'))
 760        ->execute();
 761      $path_map = ipull($path_map, 'path', 'id');
 762  
 763      foreach ($inlines as $inline) {
 764        $path = idx($path_map, $inline->getPathID());
 765        if ($path === null) {
 766          continue;
 767        }
 768  
 769        $start = $inline->getLineNumber();
 770        $len   = $inline->getLineLength();
 771        if ($len) {
 772          $range = $start.'-'.($start + $len);
 773        } else {
 774          $range = $start;
 775        }
 776  
 777        $content = $inline->getContent();
 778        $block[] = "{$path}:{$range} {$content}";
 779      }
 780  
 781      return implode("\n", $block);
 782    }
 783  
 784    public function getMailTagsMap() {
 785      return array(
 786        PhabricatorAuditTransaction::MAILTAG_COMMIT =>
 787          pht('A commit is created.'),
 788        PhabricatorAuditTransaction::MAILTAG_ACTION_CONCERN =>
 789          pht('A commit has a concerned raised against it.'),
 790        PhabricatorAuditTransaction::MAILTAG_ACTION_ACCEPT =>
 791          pht('A commit is accepted.'),
 792        PhabricatorAuditTransaction::MAILTAG_ACTION_RESIGN =>
 793          pht('A commit has an auditor resign.'),
 794        PhabricatorAuditTransaction::MAILTAG_ACTION_CLOSE =>
 795          pht('A commit is closed.'),
 796        PhabricatorAuditTransaction::MAILTAG_ADD_AUDITORS =>
 797          pht('A commit has auditors added.'),
 798        PhabricatorAuditTransaction::MAILTAG_ADD_CCS =>
 799          pht("A commit's subscribers change."),
 800        PhabricatorAuditTransaction::MAILTAG_PROJECTS =>
 801          pht("A commit's projects change."),
 802        PhabricatorAuditTransaction::MAILTAG_COMMENT =>
 803          pht('Someone comments on a commit.'),
 804        PhabricatorAuditTransaction::MAILTAG_OTHER =>
 805          pht('Other commit activity not listed above occurs.'),
 806      );
 807    }
 808  
 809  
 810  
 811    protected function shouldPublishFeedStory(
 812      PhabricatorLiskDAO $object,
 813      array $xactions) {
 814      return $this->shouldSendMail($object, $xactions);
 815    }
 816  
 817    protected function shouldApplyHeraldRules(
 818      PhabricatorLiskDAO $object,
 819      array $xactions) {
 820  
 821      foreach ($xactions as $xaction) {
 822        switch ($xaction->getTransactionType()) {
 823          case PhabricatorAuditTransaction::TYPE_COMMIT:
 824            $repository = $object->getRepository();
 825            if ($repository->isImporting()) {
 826              return false;
 827            }
 828            if ($repository->getDetail('herald-disabled')) {
 829              return false;
 830            }
 831            return true;
 832          default:
 833            break;
 834        }
 835      }
 836      return parent::shouldApplyHeraldRules($object, $xactions);
 837    }
 838  
 839    protected function buildHeraldAdapter(
 840      PhabricatorLiskDAO $object,
 841      array $xactions) {
 842  
 843      return id(new HeraldCommitAdapter())
 844        ->setCommit($object);
 845    }
 846  
 847    protected function didApplyHeraldRules(
 848      PhabricatorLiskDAO $object,
 849      HeraldAdapter $adapter,
 850      HeraldTranscript $transcript) {
 851  
 852      $xactions = array();
 853  
 854      $audit_phids = $adapter->getAuditMap();
 855      foreach ($audit_phids as $phid => $rule_ids) {
 856        foreach ($rule_ids as $rule_id) {
 857          $this->addAuditReason(
 858            $phid,
 859            pht(
 860              '%s Triggered Audit',
 861              "H{$rule_id}"));
 862        }
 863      }
 864      if ($audit_phids) {
 865        $xactions[] = id(new PhabricatorAuditTransaction())
 866          ->setTransactionType(PhabricatorAuditActionConstants::ADD_AUDITORS)
 867          ->setNewValue(array_fuse(array_keys($audit_phids)))
 868          ->setMetadataValue(
 869            'auditStatus',
 870            PhabricatorAuditStatusConstants::AUDIT_REQUIRED)
 871          ->setMetadataValue(
 872            'auditReasonMap', $this->auditReasonMap);
 873      }
 874  
 875      $cc_phids = $adapter->getAddCCMap();
 876      $add_ccs = array('+' => array());
 877      foreach ($cc_phids as $phid => $rule_ids) {
 878        $add_ccs['+'][$phid] = $phid;
 879      }
 880      $xactions[] = id(new PhabricatorAuditTransaction())
 881        ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
 882        ->setNewValue($add_ccs);
 883  
 884      $this->heraldEmailPHIDs = $adapter->getEmailPHIDs();
 885  
 886      HarbormasterBuildable::applyBuildPlans(
 887        $object->getPHID(),
 888        $object->getRepository()->getPHID(),
 889        $adapter->getBuildPlans());
 890  
 891      $limit = self::MAX_FILES_SHOWN_IN_EMAIL;
 892      $files = $adapter->loadAffectedPaths();
 893      sort($files);
 894      if (count($files) > $limit) {
 895        array_splice($files, $limit);
 896        $files[] = pht(
 897          '(This commit affected more than %d files. Only %d are shown here '.
 898          'and additional ones are truncated.)',
 899          $limit,
 900          $limit);
 901      }
 902      $this->affectedFiles = implode("\n", $files);
 903  
 904      return $xactions;
 905    }
 906  
 907    private function isCommitMostlyImported(PhabricatorLiskDAO $object) {
 908      $has_message = PhabricatorRepositoryCommit::IMPORTED_MESSAGE;
 909      $has_changes = PhabricatorRepositoryCommit::IMPORTED_CHANGE;
 910  
 911      // Don't publish feed stories or email about events which occur during
 912      // import. In particular, this affects tasks being attached when they are
 913      // closed by "Fixes Txxxx" in a commit message. See T5851.
 914  
 915      $mask = ($has_message | $has_changes);
 916  
 917      return $object->isPartiallyImported($mask);
 918    }
 919  
 920  }


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