public function receiveEmail(PhabricatorMetaMTAReceivedMail $mail)
 {
     $commit = $this->getMailReceiver();
     $actor = $this->getActor();
     // TODO: Support !raise, !accept, etc.
     // TODO: Content sources.
     $comment = id(new PhabricatorAuditComment())->setAction(PhabricatorAuditActionConstants::COMMENT)->setContent($mail->getCleanTextBody());
     $editor = new PhabricatorAuditCommentEditor($commit);
     $editor->setUser($actor);
     $editor->addComment($comment);
 }
 protected function processReceivedObjectMail(PhabricatorMetaMTAReceivedMail $mail, PhabricatorLiskDAO $object, PhabricatorUser $sender)
 {
     $handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit($object);
     $handler->setActor($sender);
     $handler->setExcludeMailRecipientPHIDs($mail->loadExcludeMailRecipientPHIDs());
     $handler->processEmail($mail);
 }
 public function loadStatus(PhabricatorUser $user)
 {
     $status = array();
     $limit = self::MAX_STATUS_ITEMS;
     $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
     $query = id(new DiffusionCommitQuery())->setViewer($user)->withAuthorPHIDs(array($user->getPHID()))->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_CONCERN)->setLimit($limit);
     $commits = $query->execute();
     $count = count($commits);
     if ($count >= $limit) {
         $count_str = pht('%s+ Problem Commits', new PhutilNumber($limit - 1));
     } else {
         $count_str = pht('%s Problem Commit(s)', new PhutilNumber($count));
     }
     $type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION;
     $status[] = id(new PhabricatorApplicationStatusView())->setType($type)->setText($count_str)->setCount($count);
     $query = id(new DiffusionCommitQuery())->setViewer($user)->withNeedsAuditByPHIDs($phids)->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN)->setLimit($limit);
     $commits = $query->execute();
     $count = count($commits);
     if ($count >= $limit) {
         $count_str = pht('%s+ Commits Awaiting Audit', new PhutilNumber($limit - 1));
     } else {
         $count_str = pht('%s Commit(s) Awaiting Audit', new PhutilNumber($count));
     }
     $type = PhabricatorApplicationStatusView::TYPE_WARNING;
     $status[] = id(new PhabricatorApplicationStatusView())->setType($type)->setText($count_str)->setCount($count);
     return $status;
 }
 public function loadStatus(PhabricatorUser $user)
 {
     $status = array();
     $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
     $audits = id(new PhabricatorAuditQuery())->withAuditorPHIDs($phids)->withStatus(PhabricatorAuditQuery::STATUS_OPEN)->withAwaitingUser($user)->execute();
     $count = count($audits);
     $type = $count ? PhabricatorApplicationStatusView::TYPE_INFO : PhabricatorApplicationStatusView::TYPE_EMPTY;
     $status[] = id(new PhabricatorApplicationStatusView())->setType($type)->setText(pht('%d Commit(s) Awaiting Audit', $count))->setCount($count);
     $commits = id(new PhabricatorAuditCommitQuery())->withAuthorPHIDs($phids)->withStatus(PhabricatorAuditQuery::STATUS_OPEN)->execute();
     $count = count($commits);
     $type = $count ? PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION : PhabricatorApplicationStatusView::TYPE_EMPTY;
     $status[] = id(new PhabricatorApplicationStatusView())->setType($type)->setText(pht('%d Problem Commit(s)', $count))->setCount($count);
     return $status;
 }
 public function loadStatus(PhabricatorUser $user)
 {
     $status = array();
     $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
     $query = id(new DiffusionCommitQuery())->setViewer($user)->withAuthorPHIDs(array($user->getPHID()))->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_CONCERN);
     $commits = $query->execute();
     $count = count($commits);
     $type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION;
     $status[] = id(new PhabricatorApplicationStatusView())->setType($type)->setText(pht('%d Problem Commit(s)', $count))->setCount($count);
     $query = id(new DiffusionCommitQuery())->setViewer($user)->withAuditorPHIDs($phids)->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN)->withAuditAwaitingUser($user);
     $commits = $query->execute();
     $count = count($commits);
     $type = PhabricatorApplicationStatusView::TYPE_WARNING;
     $status[] = id(new PhabricatorApplicationStatusView())->setType($type)->setText(pht('%d Commit(s) Awaiting Audit', $count))->setCount($count);
     return $status;
 }
 protected function renderResultList(array $commits, PhabricatorSavedQuery $query, array $handles)
 {
     assert_instances_of($commits, 'PhabricatorRepositoryCommit');
     $viewer = $this->requireViewer();
     $nodata = pht('No matching audits.');
     $view = id(new PhabricatorAuditListView())->setUser($viewer)->setCommits($commits)->setAuthorityPHIDs(PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($viewer))->setNoDataString($nodata);
     $phids = $view->getRequiredHandlePHIDs();
     if ($phids) {
         $handles = id(new PhabricatorHandleQuery())->setViewer($viewer)->withPHIDs($phids)->execute();
     } else {
         $handles = array();
     }
     $view->setHandles($handles);
     $list = $view->buildList();
     $result = new PhabricatorApplicationSearchResultView();
     $result->setContent($list);
     return $result;
 }
 public function buildAuditPanel()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
     $query = new PhabricatorAuditQuery();
     $query->withAuditorPHIDs($phids);
     $query->withStatus(PhabricatorAuditQuery::STATUS_OPEN);
     $query->withAwaitingUser($user);
     $query->needCommitData(true);
     $query->setLimit(10);
     $audits = $query->execute();
     $commits = $query->getCommits();
     if (!$audits) {
         return $this->renderMinipanel('No Audits', 'No commits are waiting for you to audit them.');
     }
     $view = new PhabricatorAuditListView();
     $view->setAudits($audits);
     $view->setCommits($commits);
     $view->setUser($user);
     $phids = $view->getRequiredHandlePHIDs();
     $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
     $view->setHandles($handles);
     $panel = new AphrontPanelView();
     $panel->setHeader('Audits');
     $panel->setCaption('Commits awaiting your audit.');
     $panel->appendChild($view);
     $panel->addButton(phutil_render_tag('a', array('href' => '/audit/', 'class' => 'button grey'), "View Active Audits »"));
     return $panel;
 }
 protected function applyFinalEffects(PhabricatorLiskDAO $object, array $xactions)
 {
     // Load auditors explicitly; we may not have them if the caller was a
     // generic piece of infrastructure.
     $commit = id(new DiffusionCommitQuery())->setViewer($this->requireActor())->withIDs(array($object->getID()))->needAuditRequests(true)->executeOne();
     if (!$commit) {
         throw new Exception(pht('Failed to load commit during transaction finalization!'));
     }
     $object->attachAudits($commit->getAudits());
     $status_concerned = PhabricatorAuditStatusConstants::CONCERNED;
     $status_closed = PhabricatorAuditStatusConstants::CLOSED;
     $status_resigned = PhabricatorAuditStatusConstants::RESIGNED;
     $status_accepted = PhabricatorAuditStatusConstants::ACCEPTED;
     $status_concerned = PhabricatorAuditStatusConstants::CONCERNED;
     $actor_phid = $this->getActingAsPHID();
     $actor_is_author = $object->getAuthorPHID() && $actor_phid == $object->getAuthorPHID();
     foreach ($xactions as $xaction) {
         switch ($xaction->getTransactionType()) {
             case PhabricatorAuditActionConstants::ACTION:
                 $new = $xaction->getNewValue();
                 switch ($new) {
                     case PhabricatorAuditActionConstants::CLOSE:
                         // "Close" means wipe out all the concerns.
                         $requests = $object->getAudits();
                         foreach ($requests as $request) {
                             if ($request->getAuditStatus() == $status_concerned) {
                                 $request->setAuditStatus($status_closed)->save();
                             }
                         }
                         break;
                     case PhabricatorAuditActionConstants::RESIGN:
                         $requests = $object->getAudits();
                         $requests = mpull($requests, null, 'getAuditorPHID');
                         $actor_request = idx($requests, $actor_phid);
                         // If the actor doesn't currently have a relationship to the
                         // commit, add one explicitly. For example, this allows members
                         // of a project to resign from a commit and have it drop out of
                         // their queue.
                         if (!$actor_request) {
                             $actor_request = id(new PhabricatorRepositoryAuditRequest())->setCommitPHID($object->getPHID())->setAuditorPHID($actor_phid);
                             $requests[] = $actor_request;
                             $object->attachAudits($requests);
                         }
                         $actor_request->setAuditStatus($status_resigned)->save();
                         break;
                     case PhabricatorAuditActionConstants::ACCEPT:
                     case PhabricatorAuditActionConstants::CONCERN:
                         if ($new == PhabricatorAuditActionConstants::ACCEPT) {
                             $new_status = $status_accepted;
                         } else {
                             $new_status = $status_concerned;
                         }
                         $requests = $object->getAudits();
                         $requests = mpull($requests, null, 'getAuditorPHID');
                         // Figure out which requests the actor has authority over: these
                         // are user requests where they are the auditor, and packages
                         // and projects they are a member of.
                         if ($actor_is_author) {
                             // When modifying your own commits, you act only on behalf of
                             // yourself, not your packages/projects -- the idea being that
                             // you can't accept your own commits.
                             $authority_phids = array($actor_phid);
                         } else {
                             $authority_phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($this->requireActor());
                         }
                         $authority = array_select_keys($requests, $authority_phids);
                         if (!$authority) {
                             // If the actor has no authority over any existing requests,
                             // create a new request for them.
                             $actor_request = id(new PhabricatorRepositoryAuditRequest())->setCommitPHID($object->getPHID())->setAuditorPHID($actor_phid)->setAuditStatus($new_status)->save();
                             $requests[$actor_phid] = $actor_request;
                             $object->attachAudits($requests);
                         } else {
                             // Otherwise, update the audit status of the existing requests.
                             foreach ($authority as $request) {
                                 $request->setAuditStatus($new_status)->save();
                             }
                         }
                         break;
                 }
                 break;
         }
     }
     $requests = $object->getAudits();
     $object->updateAuditStatus($requests);
     $object->save();
     return $xactions;
 }
 public function processRequest()
 {
     $drequest = $this->getDiffusionRequest();
     $request = $this->getRequest();
     $user = $request->getUser();
     $callsign = $drequest->getRepository()->getCallsign();
     $content = array();
     $content[] = $this->buildCrumbs(array('commit' => true));
     $repository = $drequest->getRepository();
     $commit = $drequest->loadCommit();
     if (!$commit) {
         // TODO: Make more user-friendly.
         throw new Exception('This commit has not parsed yet.');
     }
     $commit_data = $drequest->loadCommitData();
     $commit->attachCommitData($commit_data);
     $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
     if ($is_foreign) {
         $subpath = $commit_data->getCommitDetail('svn-subpath');
         $error_panel = new AphrontErrorView();
         $error_panel->setWidth(AphrontErrorView::WIDTH_WIDE);
         $error_panel->setTitle('Commit Not Tracked');
         $error_panel->setSeverity(AphrontErrorView::SEVERITY_WARNING);
         $error_panel->appendChild("This Diffusion repository is configured to track only one " . "subdirectory of the entire Subversion repository, and this commit " . "didn't affect the tracked subdirectory ('" . phutil_escape_html($subpath) . "'), so no information is available.");
         $content[] = $error_panel;
     } else {
         $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
         require_celerity_resource('diffusion-commit-view-css');
         require_celerity_resource('phabricator-remarkup-css');
         $parent_query = DiffusionCommitParentsQuery::newFromDiffusionRequest($drequest);
         $headsup_panel = new AphrontHeadsupView();
         $headsup_panel->setHeader('Commit Detail');
         $headsup_panel->setActionList($this->renderHeadsupActionList($commit));
         $headsup_panel->setProperties($this->getCommitProperties($commit, $commit_data, $parent_query->loadParents()));
         $headsup_panel->appendChild('<div class="diffusion-commit-message phabricator-remarkup">' . $engine->markupText($commit_data->getCommitMessage()) . '</div>');
         $content[] = $headsup_panel;
     }
     $query = new PhabricatorAuditQuery();
     $query->withCommitPHIDs(array($commit->getPHID()));
     $audit_requests = $query->execute();
     $this->auditAuthorityPHIDs = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
     $content[] = $this->buildAuditTable($commit, $audit_requests);
     $content[] = $this->buildComments($commit);
     $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest($drequest);
     $changes = $change_query->loadChanges();
     $content[] = $this->buildMergesTable($commit);
     $original_changes_count = count($changes);
     if ($request->getStr('show_all') !== 'true' && $original_changes_count > self::CHANGES_LIMIT) {
         $changes = array_slice($changes, 0, self::CHANGES_LIMIT);
     }
     $change_table = new DiffusionCommitChangeTableView();
     $change_table->setDiffusionRequest($drequest);
     $change_table->setPathChanges($changes);
     $count = count($changes);
     $bad_commit = null;
     if ($count == 0) {
         $bad_commit = queryfx_one(id(new PhabricatorRepository())->establishConnection('r'), 'SELECT * FROM %T WHERE fullCommitName = %s', PhabricatorRepository::TABLE_BADCOMMIT, 'r' . $callsign . $commit->getCommitIdentifier());
     }
     if ($bad_commit) {
         $error_panel = new AphrontErrorView();
         $error_panel->setWidth(AphrontErrorView::WIDTH_WIDE);
         $error_panel->setTitle('Bad Commit');
         $error_panel->appendChild(phutil_escape_html($bad_commit['description']));
         $content[] = $error_panel;
     } else {
         if ($is_foreign) {
             // Don't render anything else.
         } else {
             if (!count($changes)) {
                 $no_changes = new AphrontErrorView();
                 $no_changes->setWidth(AphrontErrorView::WIDTH_WIDE);
                 $no_changes->setSeverity(AphrontErrorView::SEVERITY_WARNING);
                 $no_changes->setTitle('Not Yet Parsed');
                 // TODO: This can also happen with weird SVN changes that don't do
                 // anything (or only alter properties?), although the real no-changes case
                 // is extremely rare and might be impossible to produce organically. We
                 // should probably write some kind of "Nothing Happened!" change into the
                 // DB once we parse these changes so we can distinguish between
                 // "not parsed yet" and "no changes".
                 $no_changes->appendChild("This commit hasn't been fully parsed yet (or doesn't affect any " . "paths).");
                 $content[] = $no_changes;
             } else {
                 $change_panel = new AphrontPanelView();
                 $change_panel->setHeader("Changes (" . number_format($count) . ")");
                 if ($count !== $original_changes_count) {
                     $show_all_button = phutil_render_tag('a', array('class' => 'button green', 'href' => '?show_all=true'), phutil_escape_html('Show All Changes'));
                     $warning_view = id(new AphrontErrorView())->setSeverity(AphrontErrorView::SEVERITY_WARNING)->setTitle(sprintf("Showing only the first %d changes out of %s!", self::CHANGES_LIMIT, number_format($original_changes_count)));
                     $change_panel->appendChild($warning_view);
                     $change_panel->addButton($show_all_button);
                 }
                 $change_panel->appendChild($change_table);
                 $content[] = $change_panel;
                 $changesets = DiffusionPathChange::convertToDifferentialChangesets($changes);
                 $vcs = $repository->getVersionControlSystem();
                 switch ($vcs) {
                     case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
                         $vcs_supports_directory_changes = true;
                         break;
                     case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
                     case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
                         $vcs_supports_directory_changes = false;
                         break;
                     default:
                         throw new Exception("Unknown VCS.");
                 }
                 $references = array();
                 foreach ($changesets as $key => $changeset) {
                     $file_type = $changeset->getFileType();
                     if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
                         if (!$vcs_supports_directory_changes) {
                             unset($changesets[$key]);
                             continue;
                         }
                     }
                     $references[$key] = $drequest->generateURI(array('action' => 'rendering-ref', 'path' => $changeset->getFilename()));
                 }
                 // TODO: Some parts of the views still rely on properties of the
                 // DifferentialChangeset. Make the objects ephemeral to make sure we don't
                 // accidentally save them, and then set their ID to the appropriate ID for
                 // this application (the path IDs).
                 $pquery = new DiffusionPathIDQuery(mpull($changesets, 'getFilename'));
                 $path_ids = $pquery->loadPathIDs();
                 foreach ($changesets as $changeset) {
                     $changeset->makeEphemeral();
                     $changeset->setID($path_ids[$changeset->getFilename()]);
                 }
                 $change_list = new DifferentialChangesetListView();
                 $change_list->setChangesets($changesets);
                 $change_list->setRenderingReferences($references);
                 $change_list->setRenderURI('/diffusion/' . $callsign . '/diff/');
                 $change_list->setRepository($repository);
                 $change_list->setUser($user);
                 $change_list->setStandaloneURI('/diffusion/' . $callsign . '/diff/');
                 $change_list->setRawFileURIs(null, '/diffusion/' . $callsign . '/diff/?view=r');
                 $change_list->setInlineCommentControllerURI('/diffusion/inline/' . phutil_escape_uri($commit->getPHID()) . '/');
                 // TODO: This is pretty awkward, unify the CSS between Diffusion and
                 // Differential better.
                 require_celerity_resource('differential-core-view-css');
                 $change_list = '<div class="differential-primary-pane">' . $change_list->render() . '</div>';
                 $content[] = $change_list;
             }
         }
     }
     $content[] = $this->buildAddCommentView($commit, $audit_requests);
     return $this->buildStandardPageResponse($content, array('title' => 'r' . $callsign . $commit->getCommitIdentifier()));
 }
 private function applyHeraldRules(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit)
 {
     $commit->attachRepository($repository);
     // Don't take any actions on an importing repository. Principally, this
     // avoids generating thousands of audits or emails when you import an
     // established repository on an existing install.
     if ($repository->isImporting()) {
         return;
     }
     if ($repository->getDetail('herald-disabled')) {
         return;
     }
     $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $commit->getID());
     if (!$data) {
         throw new PhabricatorWorkerPermanentFailureException(pht('Unable to load commit data. The data for this task is invalid ' . 'or no longer exists.'));
     }
     $adapter = id(new HeraldCommitAdapter())->setCommit($commit);
     $rules = id(new HeraldRuleQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withContentTypes(array($adapter->getAdapterContentType()))->withDisabled(false)->needConditionsAndActions(true)->needAppliedToPHIDs(array($adapter->getPHID()))->needValidateAuthors(true)->execute();
     $engine = new HeraldEngine();
     $effects = $engine->applyRules($rules, $adapter);
     $engine->applyEffects($effects, $adapter, $rules);
     $xscript = $engine->getTranscript();
     $audit_phids = $adapter->getAuditMap();
     $cc_phids = $adapter->getAddCCMap();
     if ($audit_phids || $cc_phids) {
         $this->createAudits($commit, $audit_phids, $cc_phids, $rules);
     }
     HarbormasterBuildable::applyBuildPlans($commit->getPHID(), $repository->getPHID(), $adapter->getBuildPlans());
     $explicit_auditors = $this->createAuditsFromCommitMessage($commit, $data);
     $this->publishFeedStory($repository, $commit, $data);
     $herald_targets = $adapter->getEmailPHIDs();
     $email_phids = array_unique(array_merge($explicit_auditors, array_keys($cc_phids), $herald_targets));
     if (!$email_phids) {
         return;
     }
     $revision = $adapter->loadDifferentialRevision();
     if ($revision) {
         $name = $revision->getTitle();
     } else {
         $name = $data->getSummary();
     }
     $author_phid = $data->getCommitDetail('authorPHID');
     $reviewer_phid = $data->getCommitDetail('reviewerPHID');
     $phids = array_filter(array($author_phid, $reviewer_phid, $commit->getPHID()));
     $handles = id(new PhabricatorHandleQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withPHIDs($phids)->execute();
     $commit_handle = $handles[$commit->getPHID()];
     $commit_name = $commit_handle->getName();
     if ($author_phid) {
         $author_name = $handles[$author_phid]->getName();
     } else {
         $author_name = $data->getAuthorName();
     }
     if ($reviewer_phid) {
         $reviewer_name = $handles[$reviewer_phid]->getName();
     } else {
         $reviewer_name = null;
     }
     $who = implode(', ', array_filter(array($author_name, $reviewer_name)));
     $description = $data->getCommitMessage();
     $commit_uri = PhabricatorEnv::getProductionURI($commit_handle->getURI());
     $differential = $revision ? PhabricatorEnv::getProductionURI('/D' . $revision->getID()) : 'No revision.';
     $limit = self::MAX_FILES_SHOWN_IN_EMAIL;
     $files = $adapter->loadAffectedPaths();
     sort($files);
     if (count($files) > $limit) {
         array_splice($files, $limit);
         $files[] = '(This commit affected more than ' . $limit . ' files. ' . 'Only ' . $limit . ' are shown here and additional ones are truncated.)';
     }
     $files = implode("\n", $files);
     $xscript_id = $xscript->getID();
     $why_uri = '/herald/transcript/' . $xscript_id . '/';
     $reply_handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit($commit);
     $template = new PhabricatorMetaMTAMail();
     $inline_patch_text = $this->buildPatch($template, $repository, $commit);
     $body = new PhabricatorMetaMTAMailBody();
     $body->addRawSection($description);
     $body->addTextSection(pht('DETAILS'), $commit_uri);
     // TODO: This should be integrated properly once we move to
     // ApplicationTransactions.
     $field_list = PhabricatorCustomField::getObjectFields($commit, PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS);
     $field_list->setViewer(PhabricatorUser::getOmnipotentUser())->readFieldsFromStorage($commit);
     foreach ($field_list->getFields() as $field) {
         try {
             $field->buildApplicationTransactionMailBody(new DifferentialTransaction(), $body);
         } catch (Exception $ex) {
             // Log the exception and continue.
             phlog($ex);
         }
     }
     $body->addTextSection(pht('DIFFERENTIAL REVISION'), $differential);
     $body->addTextSection(pht('AFFECTED FILES'), $files);
     $body->addReplySection($reply_handler->getReplyHandlerInstructions());
     $body->addHeraldSection($why_uri);
     $body->addRawSection($inline_patch_text);
     $body = $body->render();
     $prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix');
     $threading = PhabricatorAuditCommentEditor::getMailThreading($repository, $commit);
     list($thread_id, $thread_topic) = $threading;
     $template->setRelatedPHID($commit->getPHID());
     $template->setSubject("{$commit_name}: {$name}");
     $template->setSubjectPrefix($prefix);
     $template->setVarySubjectPrefix('[Commit]');
     $template->setBody($body);
     $template->setThreadID($thread_id, $is_new = true);
     $template->addHeader('Thread-Topic', $thread_topic);
     $template->setIsBulk(true);
     $template->addHeader('X-Herald-Rules', $xscript->getXHeraldRulesHeader());
     if ($author_phid) {
         $template->setFrom($author_phid);
     }
     // TODO: We should verify that each recipient can actually see the
     // commit before sending them email (T603).
     $mails = $reply_handler->multiplexMail($template, id(new PhabricatorHandleQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withPHIDs($email_phids)->execute(), array());
     foreach ($mails as $mail) {
         $mail->saveAndSend();
     }
 }
 public function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit)
 {
     $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $commit->getID());
     if (!$data) {
         // TODO: Permanent failure.
         return;
     }
     $rules = HeraldRule::loadAllByContentTypeWithFullData(HeraldContentTypeConfig::CONTENT_TYPE_COMMIT, $commit->getPHID());
     $adapter = new HeraldCommitAdapter($repository, $commit, $data);
     $engine = new HeraldEngine();
     $effects = $engine->applyRules($rules, $adapter);
     $engine->applyEffects($effects, $adapter, $rules);
     $audit_phids = $adapter->getAuditMap();
     if ($audit_phids) {
         $this->createAudits($commit, $audit_phids, $rules);
     }
     $explicit_auditors = $this->createAuditsFromCommitMessage($commit, $data);
     if ($repository->getDetail('herald-disabled')) {
         // This just means "disable email"; audits are (mostly) idempotent.
         return;
     }
     $this->publishFeedStory($repository, $commit, $data);
     $herald_targets = $adapter->getEmailPHIDs();
     $email_phids = array_unique(array_merge($explicit_auditors, $herald_targets));
     if (!$email_phids) {
         return;
     }
     $xscript = $engine->getTranscript();
     $revision = $adapter->loadDifferentialRevision();
     if ($revision) {
         $name = $revision->getTitle();
     } else {
         $name = $data->getSummary();
     }
     $author_phid = $data->getCommitDetail('authorPHID');
     $reviewer_phid = $data->getCommitDetail('reviewerPHID');
     $phids = array_filter(array($author_phid, $reviewer_phid, $commit->getPHID()));
     $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
     $commit_handle = $handles[$commit->getPHID()];
     $commit_name = $commit_handle->getName();
     if ($author_phid) {
         $author_name = $handles[$author_phid]->getName();
     } else {
         $author_name = $data->getAuthorName();
     }
     if ($reviewer_phid) {
         $reviewer_name = $handles[$reviewer_phid]->getName();
     } else {
         $reviewer_name = null;
     }
     $who = implode(', ', array_filter(array($author_name, $reviewer_name)));
     $description = $data->getCommitMessage();
     $commit_uri = PhabricatorEnv::getProductionURI($commit_handle->getURI());
     $differential = $revision ? PhabricatorEnv::getProductionURI('/D' . $revision->getID()) : 'No revision.';
     $files = $adapter->loadAffectedPaths();
     sort($files);
     $files = implode("\n", $files);
     $xscript_id = $xscript->getID();
     $manage_uri = '/herald/view/commits/';
     $why_uri = '/herald/transcript/' . $xscript_id . '/';
     $reply_handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit($commit);
     $template = new PhabricatorMetaMTAMail();
     $inline_patch_text = $this->buildPatch($template, $repository, $commit);
     $body = new PhabricatorMetaMTAMailBody();
     $body->addRawSection($description);
     $body->addTextSection(pht('DETAILS'), $commit_uri);
     $body->addTextSection(pht('DIFFERENTIAL REVISION'), $differential);
     $body->addTextSection(pht('AFFECTED FILES'), $files);
     $body->addReplySection($reply_handler->getReplyHandlerInstructions());
     $body->addHeraldSection($manage_uri, $why_uri);
     $body->addRawSection($inline_patch_text);
     $body = $body->render();
     $prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix');
     $threading = PhabricatorAuditCommentEditor::getMailThreading($repository, $commit);
     list($thread_id, $thread_topic) = $threading;
     $template->setRelatedPHID($commit->getPHID());
     $template->setSubject("{$commit_name}: {$name}");
     $template->setSubjectPrefix($prefix);
     $template->setVarySubjectPrefix("[Commit]");
     $template->setBody($body);
     $template->setThreadID($thread_id, $is_new = true);
     $template->addHeader('Thread-Topic', $thread_topic);
     $template->setIsBulk(true);
     $template->addHeader('X-Herald-Rules', $xscript->getXHeraldRulesHeader());
     if ($author_phid) {
         $template->setFrom($author_phid);
     }
     $mails = $reply_handler->multiplexMail($template, id(new PhabricatorObjectHandleData($email_phids))->loadHandles(), array());
     foreach ($mails as $mail) {
         $mail->saveAndSend();
     }
 }
 public function processReceivedMail()
 {
     $to = idx($this->headers, 'to');
     $to = $this->getRawEmailAddress($to);
     $from = idx($this->headers, 'from');
     $create_task = PhabricatorEnv::getEnvConfig('metamta.maniphest.public-create-email');
     if ($create_task && $to == $create_task) {
         $receiver = new ManiphestTask();
         $user = $this->lookupPublicUser();
         if ($user) {
             $this->setAuthorPHID($user->getPHID());
         } else {
             $default_author = PhabricatorEnv::getEnvConfig('metamta.maniphest.default-public-author');
             if ($default_author) {
                 $user = id(new PhabricatorUser())->loadOneWhere('username = %s', $default_author);
                 if ($user) {
                     $receiver->setOriginalEmailSource($from);
                 } else {
                     throw new Exception("Phabricator is misconfigured, the configuration key " . "'metamta.maniphest.default-public-author' is set to user " . "'{$default_author}' but that user does not exist.");
                 }
             } else {
                 // TODO: We should probably bounce these since from the user's
                 // perspective their email vanishes into a black hole.
                 return $this->setMessage("Invalid public user '{$from}'.")->save();
             }
         }
         $receiver->setAuthorPHID($user->getPHID());
         $receiver->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
         $editor = new ManiphestTransactionEditor();
         $handler = $editor->buildReplyHandler($receiver);
         $handler->setActor($user);
         $handler->receiveEmail($this);
         $this->setRelatedPHID($receiver->getPHID());
         $this->setMessage('OK');
         return $this->save();
     }
     // We've already stripped this, so look for an object address which has
     // a format like: D291+291+b0a41ca848d66dcc@example.com
     $matches = null;
     $single_handle_prefix = PhabricatorEnv::getEnvConfig('metamta.single-reply-handler-prefix');
     $prefixPattern = $single_handle_prefix ? preg_quote($single_handle_prefix, '/') . '\\+' : '';
     $pattern = "/^{$prefixPattern}((?:D|T|C)\\d+)\\+([\\w]+)\\+([a-f0-9]{16})@/U";
     $ok = preg_match($pattern, $to, $matches);
     if (!$ok) {
         return $this->setMessage("Unrecognized 'to' format: {$to}")->save();
     }
     $receiver_name = $matches[1];
     $user_id = $matches[2];
     $hash = $matches[3];
     if ($user_id == 'public') {
         if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) {
             return $this->setMessage("Public replies not enabled.")->save();
         }
         $user = $this->lookupPublicUser();
         if (!$user) {
             return $this->setMessage("Invalid public user '{$from}'.")->save();
         }
         $use_user_hash = false;
     } else {
         $user = id(new PhabricatorUser())->load($user_id);
         if (!$user) {
             return $this->setMessage("Invalid private user '{$user_id}'.")->save();
         }
         $use_user_hash = true;
     }
     if ($user->getIsDisabled()) {
         return $this->setMessage("User '{$user_id}' is disabled")->save();
     }
     $this->setAuthorPHID($user->getPHID());
     $receiver = self::loadReceiverObject($receiver_name);
     if (!$receiver) {
         return $this->setMessage("Invalid object '{$receiver_name}'")->save();
     }
     $this->setRelatedPHID($receiver->getPHID());
     if ($use_user_hash) {
         // This is a private reply-to address, check that the user hash is
         // correct.
         $check_phid = $user->getPHID();
     } else {
         // This is a public reply-to address, check that the object hash is
         // correct.
         $check_phid = $receiver->getPHID();
     }
     $expect_hash = self::computeMailHash($receiver->getMailKey(), $check_phid);
     // See note at computeOldMailHash().
     $old_hash = self::computeOldMailHash($receiver->getMailKey(), $check_phid);
     if ($expect_hash != $hash && $old_hash != $hash) {
         return $this->setMessage("Invalid mail hash!")->save();
     }
     if ($receiver instanceof ManiphestTask) {
         $editor = new ManiphestTransactionEditor();
         $handler = $editor->buildReplyHandler($receiver);
     } else {
         if ($receiver instanceof DifferentialRevision) {
             $handler = DifferentialMail::newReplyHandlerForRevision($receiver);
         } else {
             if ($receiver instanceof PhabricatorRepositoryCommit) {
                 $handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit($receiver);
             }
         }
     }
     $handler->setActor($user);
     $handler->receiveEmail($this);
     $this->setMessage('OK');
     return $this->save();
 }
 public function handleRequest(AphrontRequest $request)
 {
     $response = $this->loadDiffusionContext();
     if ($response) {
         return $response;
     }
     $drequest = $this->getDiffusionRequest();
     $viewer = $request->getUser();
     if ($request->getStr('diff')) {
         return $this->buildRawDiffResponse($drequest);
     }
     $repository = $drequest->getRepository();
     $commit = id(new DiffusionCommitQuery())->setViewer($viewer)->withRepository($repository)->withIdentifiers(array($drequest->getCommit()))->needCommitData(true)->needAuditRequests(true)->executeOne();
     $crumbs = $this->buildCrumbs(array('commit' => true));
     $crumbs->setBorder(true);
     if (!$commit) {
         if (!$this->getCommitExists()) {
             return new Aphront404Response();
         }
         $error = id(new PHUIInfoView())->setTitle(pht('Commit Still Parsing'))->appendChild(pht('Failed to load the commit because the commit has not been ' . 'parsed yet.'));
         $title = pht('Commit Still Parsing');
         return $this->newPage()->setTitle($title)->setCrumbs($crumbs)->appendChild($error);
     }
     $audit_requests = $commit->getAudits();
     $this->auditAuthorityPHIDs = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($viewer);
     $commit_data = $commit->getCommitData();
     $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
     $error_panel = null;
     if ($is_foreign) {
         $subpath = $commit_data->getCommitDetail('svn-subpath');
         $error_panel = new PHUIInfoView();
         $error_panel->setTitle(pht('Commit Not Tracked'));
         $error_panel->setSeverity(PHUIInfoView::SEVERITY_WARNING);
         $error_panel->appendChild(pht("This Diffusion repository is configured to track only one " . "subdirectory of the entire Subversion repository, and this commit " . "didn't affect the tracked subdirectory ('%s'), so no " . "information is available.", $subpath));
     } else {
         $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
         $engine->setConfig('viewer', $viewer);
         $commit_tag = $this->renderCommitHashTag($drequest);
         $header = id(new PHUIHeaderView())->setHeader(nonempty($commit->getSummary(), pht('Commit Detail')))->setHeaderIcon('fa-code-fork')->addTag($commit_tag);
         if ($commit->getAuditStatus()) {
             $icon = PhabricatorAuditCommitStatusConstants::getStatusIcon($commit->getAuditStatus());
             $color = PhabricatorAuditCommitStatusConstants::getStatusColor($commit->getAuditStatus());
             $status = PhabricatorAuditCommitStatusConstants::getStatusName($commit->getAuditStatus());
             $header->setStatus($icon, $color, $status);
         }
         $curtain = $this->buildCurtain($commit, $repository);
         $subheader = $this->buildSubheaderView($commit, $commit_data);
         $details = $this->buildPropertyListView($commit, $commit_data, $audit_requests);
         $message = $commit_data->getCommitMessage();
         $revision = $commit->getCommitIdentifier();
         $message = $this->linkBugtraq($message);
         $message = $engine->markupText($message);
         $detail_list = new PHUIPropertyListView();
         $detail_list->addTextContent(phutil_tag('div', array('class' => 'diffusion-commit-message phabricator-remarkup'), $message));
         if ($this->getCommitErrors()) {
             $error_panel = id(new PHUIInfoView())->appendChild($this->getCommitErrors())->setSeverity(PHUIInfoView::SEVERITY_WARNING);
         }
     }
     $timeline = $this->buildComments($commit);
     $hard_limit = 1000;
     if ($commit->isImported()) {
         $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest($drequest);
         $change_query->setLimit($hard_limit + 1);
         $changes = $change_query->loadChanges();
     } else {
         $changes = array();
     }
     $was_limited = count($changes) > $hard_limit;
     if ($was_limited) {
         $changes = array_slice($changes, 0, $hard_limit);
     }
     $merge_table = $this->buildMergesTable($commit);
     $highlighted_audits = $commit->getAuthorityAudits($viewer, $this->auditAuthorityPHIDs);
     $count = count($changes);
     $bad_commit = null;
     if ($count == 0) {
         $bad_commit = queryfx_one(id(new PhabricatorRepository())->establishConnection('r'), 'SELECT * FROM %T WHERE fullCommitName = %s', PhabricatorRepository::TABLE_BADCOMMIT, $commit->getMonogram());
     }
     $show_changesets = false;
     $info_panel = null;
     $change_list = null;
     $change_table = null;
     if ($bad_commit) {
         $info_panel = $this->renderStatusMessage(pht('Bad Commit'), $bad_commit['description']);
     } else {
         if ($is_foreign) {
             // Don't render anything else.
         } else {
             if (!$commit->isImported()) {
                 $info_panel = $this->renderStatusMessage(pht('Still Importing...'), pht('This commit is still importing. Changes will be visible once ' . 'the import finishes.'));
             } else {
                 if (!count($changes)) {
                     $info_panel = $this->renderStatusMessage(pht('Empty Commit'), pht('This commit is empty and does not affect any paths.'));
                 } else {
                     if ($was_limited) {
                         $info_panel = $this->renderStatusMessage(pht('Enormous Commit'), pht('This commit is enormous, and affects more than %d files. ' . 'Changes are not shown.', $hard_limit));
                     } else {
                         if (!$this->getCommitExists()) {
                             $info_panel = $this->renderStatusMessage(pht('Commit No Longer Exists'), pht('This commit no longer exists in the repository.'));
                         } else {
                             $show_changesets = true;
                             // The user has clicked "Show All Changes", and we should show all the
                             // changes inline even if there are more than the soft limit.
                             $show_all_details = $request->getBool('show_all');
                             $change_header = id(new PHUIHeaderView())->setHeader(pht('Changes (%s)', new PhutilNumber($count)));
                             $warning_view = null;
                             if ($count > self::CHANGES_LIMIT && !$show_all_details) {
                                 $button = id(new PHUIButtonView())->setText(pht('Show All Changes'))->setHref('?show_all=true')->setTag('a')->setIcon('fa-files-o');
                                 $warning_view = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_WARNING)->setTitle(pht('Very Large Commit'))->appendChild(pht('This commit is very large. Load each file individually.'));
                                 $change_header->addActionLink($button);
                             }
                             $changesets = DiffusionPathChange::convertToDifferentialChangesets($viewer, $changes);
                             // TODO: This table and panel shouldn't really be separate, but we need
                             // to clean up the "Load All Files" interaction first.
                             $change_table = $this->buildTableOfContents($changesets, $change_header, $warning_view);
                             $vcs = $repository->getVersionControlSystem();
                             switch ($vcs) {
                                 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
                                     $vcs_supports_directory_changes = true;
                                     break;
                                 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
                                 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
                                     $vcs_supports_directory_changes = false;
                                     break;
                                 default:
                                     throw new Exception(pht('Unknown VCS.'));
                             }
                             $references = array();
                             foreach ($changesets as $key => $changeset) {
                                 $file_type = $changeset->getFileType();
                                 if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
                                     if (!$vcs_supports_directory_changes) {
                                         unset($changesets[$key]);
                                         continue;
                                     }
                                 }
                                 $references[$key] = $drequest->generateURI(array('action' => 'rendering-ref', 'path' => $changeset->getFilename()));
                             }
                             // TODO: Some parts of the views still rely on properties of the
                             // DifferentialChangeset. Make the objects ephemeral to make sure we don't
                             // accidentally save them, and then set their ID to the appropriate ID for
                             // this application (the path IDs).
                             $path_ids = array_flip(mpull($changes, 'getPath'));
                             foreach ($changesets as $changeset) {
                                 $changeset->makeEphemeral();
                                 $changeset->setID($path_ids[$changeset->getFilename()]);
                             }
                             if ($count <= self::CHANGES_LIMIT || $show_all_details) {
                                 $visible_changesets = $changesets;
                             } else {
                                 $visible_changesets = array();
                                 $inlines = PhabricatorAuditInlineComment::loadDraftAndPublishedComments($viewer, $commit->getPHID());
                                 $path_ids = mpull($inlines, null, 'getPathID');
                                 foreach ($changesets as $key => $changeset) {
                                     if (array_key_exists($changeset->getID(), $path_ids)) {
                                         $visible_changesets[$key] = $changeset;
                                     }
                                 }
                             }
                             $change_list_title = $commit->getDisplayName();
                             $change_list = new DifferentialChangesetListView();
                             $change_list->setTitle($change_list_title);
                             $change_list->setChangesets($changesets);
                             $change_list->setVisibleChangesets($visible_changesets);
                             $change_list->setRenderingReferences($references);
                             $change_list->setRenderURI($repository->getPathURI('diff/'));
                             $change_list->setRepository($repository);
                             $change_list->setUser($viewer);
                             $change_list->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
                             // TODO: Try to setBranch() to something reasonable here?
                             $change_list->setStandaloneURI($repository->getPathURI('diff/'));
                             $change_list->setRawFileURIs(null, $repository->getPathURI('diff/?view=r'));
                             $change_list->setInlineCommentControllerURI('/diffusion/inline/edit/' . phutil_escape_uri($commit->getPHID()) . '/');
                         }
                     }
                 }
             }
         }
     }
     $add_comment = $this->renderAddCommentPanel($commit, $audit_requests);
     $filetree_on = $viewer->compareUserSetting(PhabricatorShowFiletreeSetting::SETTINGKEY, PhabricatorShowFiletreeSetting::VALUE_ENABLE_FILETREE);
     $pref_collapse = PhabricatorFiletreeVisibleSetting::SETTINGKEY;
     $collapsed = $viewer->getUserSetting($pref_collapse);
     $nav = null;
     if ($show_changesets && $filetree_on) {
         $nav = id(new DifferentialChangesetFileTreeSideNavBuilder())->setTitle($commit->getDisplayName())->setBaseURI(new PhutilURI($commit->getURI()))->build($changesets)->setCrumbs($crumbs)->setCollapsed((bool) $collapsed);
     }
     $view = id(new PHUITwoColumnView())->setHeader($header)->setSubheader($subheader)->setMainColumn(array($error_panel, $timeline, $merge_table, $info_panel))->setFooter(array($change_table, $change_list, $add_comment))->addPropertySection(pht('Description'), $detail_list)->addPropertySection(pht('Details'), $details)->setCurtain($curtain);
     $page = $this->newPage()->setTitle($commit->getDisplayName())->setCrumbs($crumbs)->setPageObjectPHIDS(array($commit->getPHID()))->appendChild(array($view));
     if ($nav) {
         $page->setNavigation($nav);
     }
     return $page;
 }
 public function processReceivedMail()
 {
     // If Phabricator sent the mail, always drop it immediately. This prevents
     // loops where, e.g., the public bug address is also a user email address
     // and creating a bug sends them an email, which loops.
     $is_phabricator_mail = idx($this->headers, 'x-phabricator-sent-this-message');
     if ($is_phabricator_mail) {
         $message = "Ignoring email with 'X-Phabricator-Sent-This-Message' " . "header to avoid loops.";
         return $this->setMessage($message)->save();
     }
     list($to, $receiver_name, $user_id, $hash) = $this->getPhabricatorToInformation();
     if (!$to) {
         $raw_to = idx($this->headers, 'to');
         return $this->setMessage("Unrecognized 'to' format: {$raw_to}")->save();
     }
     $from = idx($this->headers, 'from');
     // TODO -- make this a switch statement / better if / when we add more
     // public create email addresses!
     $create_task = PhabricatorEnv::getEnvConfig('metamta.maniphest.public-create-email');
     if ($create_task && $to == $create_task) {
         $receiver = new ManiphestTask();
         $user = $this->lookupPublicUser();
         if ($user) {
             $this->setAuthorPHID($user->getPHID());
         } else {
             $default_author = PhabricatorEnv::getEnvConfig('metamta.maniphest.default-public-author');
             if ($default_author) {
                 $user = id(new PhabricatorUser())->loadOneWhere('username = %s', $default_author);
                 if ($user) {
                     $receiver->setOriginalEmailSource($from);
                 } else {
                     throw new Exception("Phabricator is misconfigured, the configuration key " . "'metamta.maniphest.default-public-author' is set to user " . "'{$default_author}' but that user does not exist.");
                 }
             } else {
                 // TODO: We should probably bounce these since from the user's
                 // perspective their email vanishes into a black hole.
                 return $this->setMessage("Invalid public user '{$from}'.")->save();
             }
         }
         $receiver->setAuthorPHID($user->getPHID());
         $receiver->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
         $editor = new ManiphestTransactionEditor();
         $handler = $editor->buildReplyHandler($receiver);
         $handler->setActor($user);
         $handler->processEmail($this);
         $this->setRelatedPHID($receiver->getPHID());
         $this->setMessage('OK');
         return $this->save();
     }
     if ($user_id == 'public') {
         if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) {
             return $this->setMessage("Public replies not enabled.")->save();
         }
         $user = $this->lookupPublicUser();
         if (!$user) {
             return $this->setMessage("Invalid public user '{$from}'.")->save();
         }
         $use_user_hash = false;
     } else {
         $user = id(new PhabricatorUser())->load($user_id);
         if (!$user) {
             return $this->setMessage("Invalid private user '{$user_id}'.")->save();
         }
         $use_user_hash = true;
     }
     if ($user->getIsDisabled()) {
         return $this->setMessage("User '{$user_id}' is disabled")->save();
     }
     $this->setAuthorPHID($user->getPHID());
     $receiver = self::loadReceiverObject($receiver_name);
     if (!$receiver) {
         return $this->setMessage("Invalid object '{$receiver_name}'")->save();
     }
     $this->setRelatedPHID($receiver->getPHID());
     if ($use_user_hash) {
         // This is a private reply-to address, check that the user hash is
         // correct.
         $check_phid = $user->getPHID();
     } else {
         // This is a public reply-to address, check that the object hash is
         // correct.
         $check_phid = $receiver->getPHID();
     }
     $expect_hash = self::computeMailHash($receiver->getMailKey(), $check_phid);
     // See note at computeOldMailHash().
     $old_hash = self::computeOldMailHash($receiver->getMailKey(), $check_phid);
     if ($expect_hash != $hash && $old_hash != $hash) {
         return $this->setMessage("Invalid mail hash!")->save();
     }
     if ($receiver instanceof ManiphestTask) {
         $editor = new ManiphestTransactionEditor();
         $handler = $editor->buildReplyHandler($receiver);
     } else {
         if ($receiver instanceof DifferentialRevision) {
             $handler = DifferentialMail::newReplyHandlerForRevision($receiver);
         } else {
             if ($receiver instanceof PhabricatorRepositoryCommit) {
                 $handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit($receiver);
             }
         }
     }
     $handler->setActor($user);
     $handler->processEmail($this);
     $this->setMessage('OK');
     return $this->save();
 }
 private function buildAuditView(PhabricatorObjectHandle $handle = null)
 {
     $request = $this->getRequest();
     $query = new PhabricatorAuditQuery();
     $use_pager = $this->filter != 'active';
     if ($use_pager) {
         $pager = new AphrontPagerView();
         $pager->setURI($request->getRequestURI(), 'offset');
         $pager->setOffset($request->getInt('offset'));
         $query->setOffset($pager->getOffset());
         $query->setLimit($pager->getPageSize() + 1);
     }
     $awaiting = null;
     $phids = null;
     switch ($this->filter) {
         case 'user':
         case 'active':
             $obj = id(new PhabricatorUser())->loadOneWhere('phid = %s', $handle->getPHID());
             if (!$obj) {
                 throw new Exception("Invalid user!");
             }
             $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($obj);
             $awaiting = $obj;
             break;
         case 'project':
         case 'package':
             $phids = array($handle->getPHID());
             break;
         case 'audits':
             break;
         default:
             throw new Exception("Unknown filter!");
     }
     if ($phids) {
         $query->withAuditorPHIDs($phids);
     }
     if ($awaiting) {
         $query->withAwaitingUser($awaiting);
     }
     switch ($this->filter) {
         case 'audits':
         case 'user':
         case 'project':
         case 'package':
             switch ($this->filterStatus) {
                 case 'open':
                     $query->withStatus(PhabricatorAuditQuery::STATUS_OPEN);
                     break;
             }
             break;
         case 'active':
             $query->withStatus(PhabricatorAuditQuery::STATUS_OPEN);
             break;
     }
     if ($handle) {
         $handle_name = phutil_escape_html($handle->getName());
     } else {
         $handle_name = null;
     }
     switch ($this->filter) {
         case 'active':
             $header = 'Required Audits';
             $nodata = 'No commits require your audit.';
             break;
         case 'user':
             $header = "Audits for {$handle_name}";
             $nodata = "No matching audits by {$handle_name}.";
             break;
         case 'audits':
             $header = "Audits";
             $nodata = "No matching audits.";
             break;
         case 'project':
             $header = "Audits in Project '{$handle_name}'";
             $nodata = "No matching audits in project '{$handle_name}'.";
             break;
         case 'package':
             $header = "Audits for Package '{$handle_name}'";
             $nodata = "No matching audits in package '{$handle_name}'.";
             break;
     }
     $query->needCommitData(true);
     $audits = $query->execute();
     if ($use_pager) {
         $audits = $pager->sliceResults($audits);
     }
     $view = new PhabricatorAuditListView();
     $view->setAudits($audits);
     $view->setCommits($query->getCommits());
     $view->setNoDataString($nodata);
     $phids = $view->getRequiredHandlePHIDs();
     $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
     $view->setHandles($handles);
     $panel = new AphrontPanelView();
     $panel->setHeader($header);
     $panel->appendChild($view);
     if ($use_pager) {
         $panel->appendChild($pager);
     }
     return $panel;
 }
 public function buildAuditPanel()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
     $query = id(new DiffusionCommitQuery())->setViewer($user)->withAuditorPHIDs($phids)->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN)->withAuditAwaitingUser($user)->needAuditRequests(true)->needCommitData(true)->setLimit(10);
     $commits = $query->execute();
     if (!$commits) {
         return $this->renderMinipanel('No Audits', 'No commits are waiting for you to audit them.');
     }
     $view = id(new PhabricatorAuditListView())->setCommits($commits)->setUser($user);
     $phids = $view->getRequiredHandlePHIDs();
     $handles = $this->loadViewerHandles($phids);
     $view->setHandles($handles);
     $title = pht('Audits');
     $href = '/audit/';
     $panel = new AphrontPanelView();
     $panel->setHeader($this->renderSectionHeader($title, $href));
     $panel->appendChild($view);
     $panel->setNoBackground();
     return $panel;
 }
 protected function processDiffusionRequest(AphrontRequest $request)
 {
     $user = $request->getUser();
     // This controller doesn't use blob/path stuff, just pass the dictionary
     // in directly instead of using the AphrontRequest parsing mechanism.
     $data = $request->getURIMap();
     $data['user'] = $user;
     $drequest = DiffusionRequest::newFromDictionary($data);
     $this->diffusionRequest = $drequest;
     if ($request->getStr('diff')) {
         return $this->buildRawDiffResponse($drequest);
     }
     $repository = $drequest->getRepository();
     $callsign = $repository->getCallsign();
     $content = array();
     $commit = id(new DiffusionCommitQuery())->setViewer($request->getUser())->withRepository($repository)->withIdentifiers(array($drequest->getCommit()))->needCommitData(true)->needAuditRequests(true)->executeOne();
     $crumbs = $this->buildCrumbs(array('commit' => true));
     if (!$commit) {
         $exists = $this->callConduitWithDiffusionRequest('diffusion.existsquery', array('commit' => $drequest->getCommit()));
         if (!$exists) {
             return new Aphront404Response();
         }
         $error = id(new PHUIInfoView())->setTitle(pht('Commit Still Parsing'))->appendChild(pht('Failed to load the commit because the commit has not been ' . 'parsed yet.'));
         return $this->buildApplicationPage(array($crumbs, $error), array('title' => pht('Commit Still Parsing')));
     }
     $audit_requests = $commit->getAudits();
     $this->auditAuthorityPHIDs = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
     $commit_data = $commit->getCommitData();
     $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
     if ($is_foreign) {
         $subpath = $commit_data->getCommitDetail('svn-subpath');
         $error_panel = new PHUIInfoView();
         $error_panel->setTitle(pht('Commit Not Tracked'));
         $error_panel->setSeverity(PHUIInfoView::SEVERITY_WARNING);
         $error_panel->appendChild(pht("This Diffusion repository is configured to track only one " . "subdirectory of the entire Subversion repository, and this commit " . "didn't affect the tracked subdirectory ('%s'), so no " . "information is available.", $subpath));
         $content[] = $error_panel;
     } else {
         $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
         $engine->setConfig('viewer', $user);
         require_celerity_resource('phabricator-remarkup-css');
         $parents = $this->callConduitWithDiffusionRequest('diffusion.commitparentsquery', array('commit' => $drequest->getCommit()));
         if ($parents) {
             $parents = id(new DiffusionCommitQuery())->setViewer($user)->withRepository($repository)->withIdentifiers($parents)->execute();
         }
         $headsup_view = id(new PHUIHeaderView())->setHeader(nonempty($commit->getSummary(), pht('Commit Detail')));
         $headsup_actions = $this->renderHeadsupActionList($commit, $repository);
         $commit_properties = $this->loadCommitProperties($commit, $commit_data, $parents, $audit_requests);
         $property_list = id(new PHUIPropertyListView())->setHasKeyboardShortcuts(true)->setUser($user)->setObject($commit);
         foreach ($commit_properties as $key => $value) {
             $property_list->addProperty($key, $value);
         }
         $message = $commit_data->getCommitMessage();
         $revision = $commit->getCommitIdentifier();
         $message = $this->linkBugtraq($message);
         $message = $engine->markupText($message);
         $property_list->invokeWillRenderEvent();
         $property_list->setActionList($headsup_actions);
         $detail_list = new PHUIPropertyListView();
         $detail_list->addSectionHeader(pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
         $detail_list->addTextContent(phutil_tag('div', array('class' => 'diffusion-commit-message phabricator-remarkup'), $message));
         $headsup_view->setTall(true);
         $object_box = id(new PHUIObjectBoxView())->setHeader($headsup_view)->addPropertyList($property_list)->addPropertyList($detail_list);
         $content[] = $object_box;
     }
     $content[] = $this->buildComments($commit);
     $hard_limit = 1000;
     if ($commit->isImported()) {
         $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest($drequest);
         $change_query->setLimit($hard_limit + 1);
         $changes = $change_query->loadChanges();
     } else {
         $changes = array();
     }
     $was_limited = count($changes) > $hard_limit;
     if ($was_limited) {
         $changes = array_slice($changes, 0, $hard_limit);
     }
     $content[] = $this->buildMergesTable($commit);
     $highlighted_audits = $commit->getAuthorityAudits($user, $this->auditAuthorityPHIDs);
     $count = count($changes);
     $bad_commit = null;
     if ($count == 0) {
         $bad_commit = queryfx_one(id(new PhabricatorRepository())->establishConnection('r'), 'SELECT * FROM %T WHERE fullCommitName = %s', PhabricatorRepository::TABLE_BADCOMMIT, 'r' . $callsign . $commit->getCommitIdentifier());
     }
     $show_changesets = false;
     if ($bad_commit) {
         $content[] = $this->renderStatusMessage(pht('Bad Commit'), $bad_commit['description']);
     } else {
         if ($is_foreign) {
             // Don't render anything else.
         } else {
             if (!$commit->isImported()) {
                 $content[] = $this->renderStatusMessage(pht('Still Importing...'), pht('This commit is still importing. Changes will be visible once ' . 'the import finishes.'));
             } else {
                 if (!count($changes)) {
                     $content[] = $this->renderStatusMessage(pht('Empty Commit'), pht('This commit is empty and does not affect any paths.'));
                 } else {
                     if ($was_limited) {
                         $content[] = $this->renderStatusMessage(pht('Enormous Commit'), pht('This commit is enormous, and affects more than %d files. ' . 'Changes are not shown.', $hard_limit));
                     } else {
                         $show_changesets = true;
                         // The user has clicked "Show All Changes", and we should show all the
                         // changes inline even if there are more than the soft limit.
                         $show_all_details = $request->getBool('show_all');
                         $change_panel = new PHUIObjectBoxView();
                         $header = new PHUIHeaderView();
                         $header->setHeader(pht('Changes (%s)', new PhutilNumber($count)));
                         $change_panel->setID('toc');
                         if ($count > self::CHANGES_LIMIT && !$show_all_details) {
                             $icon = id(new PHUIIconView())->setIconFont('fa-files-o');
                             $button = id(new PHUIButtonView())->setText(pht('Show All Changes'))->setHref('?show_all=true')->setTag('a')->setIcon($icon);
                             $warning_view = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_WARNING)->setTitle(pht('Very Large Commit'))->appendChild(pht('This commit is very large. Load each file individually.'));
                             $change_panel->setInfoView($warning_view);
                             $header->addActionLink($button);
                         }
                         $changesets = DiffusionPathChange::convertToDifferentialChangesets($user, $changes);
                         // TODO: This table and panel shouldn't really be separate, but we need
                         // to clean up the "Load All Files" interaction first.
                         $change_table = $this->buildTableOfContents($changesets);
                         $change_panel->setTable($change_table);
                         $change_panel->setHeader($header);
                         $content[] = $change_panel;
                         $vcs = $repository->getVersionControlSystem();
                         switch ($vcs) {
                             case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
                                 $vcs_supports_directory_changes = true;
                                 break;
                             case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
                             case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
                                 $vcs_supports_directory_changes = false;
                                 break;
                             default:
                                 throw new Exception(pht('Unknown VCS.'));
                         }
                         $references = array();
                         foreach ($changesets as $key => $changeset) {
                             $file_type = $changeset->getFileType();
                             if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
                                 if (!$vcs_supports_directory_changes) {
                                     unset($changesets[$key]);
                                     continue;
                                 }
                             }
                             $references[$key] = $drequest->generateURI(array('action' => 'rendering-ref', 'path' => $changeset->getFilename()));
                         }
                         // TODO: Some parts of the views still rely on properties of the
                         // DifferentialChangeset. Make the objects ephemeral to make sure we don't
                         // accidentally save them, and then set their ID to the appropriate ID for
                         // this application (the path IDs).
                         $path_ids = array_flip(mpull($changes, 'getPath'));
                         foreach ($changesets as $changeset) {
                             $changeset->makeEphemeral();
                             $changeset->setID($path_ids[$changeset->getFilename()]);
                         }
                         if ($count <= self::CHANGES_LIMIT || $show_all_details) {
                             $visible_changesets = $changesets;
                         } else {
                             $visible_changesets = array();
                             $inlines = PhabricatorAuditInlineComment::loadDraftAndPublishedComments($user, $commit->getPHID());
                             $path_ids = mpull($inlines, null, 'getPathID');
                             foreach ($changesets as $key => $changeset) {
                                 if (array_key_exists($changeset->getID(), $path_ids)) {
                                     $visible_changesets[$key] = $changeset;
                                 }
                             }
                         }
                         $change_list_title = DiffusionView::nameCommit($repository, $commit->getCommitIdentifier());
                         $change_list = new DifferentialChangesetListView();
                         $change_list->setTitle($change_list_title);
                         $change_list->setChangesets($changesets);
                         $change_list->setVisibleChangesets($visible_changesets);
                         $change_list->setRenderingReferences($references);
                         $change_list->setRenderURI('/diffusion/' . $callsign . '/diff/');
                         $change_list->setRepository($repository);
                         $change_list->setUser($user);
                         // TODO: Try to setBranch() to something reasonable here?
                         $change_list->setStandaloneURI('/diffusion/' . $callsign . '/diff/');
                         $change_list->setRawFileURIs(null, '/diffusion/' . $callsign . '/diff/?view=r');
                         $change_list->setInlineCommentControllerURI('/diffusion/inline/edit/' . phutil_escape_uri($commit->getPHID()) . '/');
                         $content[] = $change_list->render();
                     }
                 }
             }
         }
     }
     $content[] = $this->renderAddCommentPanel($commit, $audit_requests);
     $commit_id = 'r' . $callsign . $commit->getCommitIdentifier();
     $short_name = DiffusionView::nameCommit($repository, $commit->getCommitIdentifier());
     $prefs = $user->loadPreferences();
     $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE;
     $pref_collapse = PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED;
     $show_filetree = $prefs->getPreference($pref_filetree);
     $collapsed = $prefs->getPreference($pref_collapse);
     if ($show_changesets && $show_filetree) {
         $nav = id(new DifferentialChangesetFileTreeSideNavBuilder())->setTitle($short_name)->setBaseURI(new PhutilURI('/' . $commit_id))->build($changesets)->setCrumbs($crumbs)->setCollapsed((bool) $collapsed)->appendChild($content);
         $content = $nav;
     } else {
         $content = array($crumbs, $content);
     }
     return $this->buildApplicationPage($content, array('title' => $commit_id, 'pageObjects' => array($commit->getPHID())));
 }
    public function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit)
    {
        $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $commit->getID());
        if (!$data) {
            // TODO: Permanent failure.
            return;
        }
        $rules = HeraldRule::loadAllByContentTypeWithFullData(HeraldContentTypeConfig::CONTENT_TYPE_COMMIT, $commit->getPHID());
        $adapter = new HeraldCommitAdapter($repository, $commit, $data);
        $engine = new HeraldEngine();
        $effects = $engine->applyRules($rules, $adapter);
        $engine->applyEffects($effects, $adapter, $rules);
        $audit_phids = $adapter->getAuditMap();
        if ($audit_phids) {
            $this->createAudits($commit, $audit_phids, $rules);
        }
        $this->createAuditsFromCommitMessage($commit, $data);
        $email_phids = $adapter->getEmailPHIDs();
        if (!$email_phids) {
            return;
        }
        if ($repository->getDetail('herald-disabled')) {
            // This just means "disable email"; audits are (mostly) idempotent.
            return;
        }
        $xscript = $engine->getTranscript();
        $revision = $adapter->loadDifferentialRevision();
        if ($revision) {
            $name = $revision->getTitle();
        } else {
            $name = $data->getSummary();
        }
        $author_phid = $data->getCommitDetail('authorPHID');
        $reviewer_phid = $data->getCommitDetail('reviewerPHID');
        $phids = array_filter(array($author_phid, $reviewer_phid, $commit->getPHID()));
        $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
        $commit_handle = $handles[$commit->getPHID()];
        $commit_name = $commit_handle->getName();
        if ($author_phid) {
            $author_name = $handles[$author_phid]->getName();
        } else {
            $author_name = $data->getAuthorName();
        }
        if ($reviewer_phid) {
            $reviewer_name = $handles[$reviewer_phid]->getName();
        } else {
            $reviewer_name = null;
        }
        $who = implode(', ', array_filter(array($author_name, $reviewer_name)));
        $description = $data->getCommitMessage();
        $commit_uri = PhabricatorEnv::getProductionURI($commit_handle->getURI());
        $differential = $revision ? PhabricatorEnv::getProductionURI('/D' . $revision->getID()) : 'No revision.';
        $files = $adapter->loadAffectedPaths();
        sort($files);
        $files = implode("\n  ", $files);
        $xscript_id = $xscript->getID();
        $manage_uri = PhabricatorEnv::getProductionURI('/herald/view/commits/');
        $why_uri = PhabricatorEnv::getProductionURI('/herald/transcript/' . $xscript_id . '/');
        $reply_handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit($commit);
        $reply_instructions = $reply_handler->getReplyHandlerInstructions();
        if ($reply_instructions) {
            $reply_instructions = "\n" . "REPLY HANDLER ACTIONS\n" . "  " . $reply_instructions . "\n";
        }
        $body = <<<EOBODY
DESCRIPTION
{$description}

DETAILS
  {$commit_uri}

DIFFERENTIAL REVISION
  {$differential}

AFFECTED FILES
  {$files}
{$reply_instructions}
MANAGE HERALD COMMIT RULES
  {$manage_uri}

WHY DID I GET THIS EMAIL?
  {$why_uri}

EOBODY;
        $prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix');
        $subject = trim("{$prefix} {$commit_name}: {$name}");
        $vary_subject = trim("{$prefix} [Commit] {$commit_name}: {$name}");
        $threading = PhabricatorAuditCommentEditor::getMailThreading($commit->getPHID());
        list($thread_id, $thread_topic) = $threading;
        $template = new PhabricatorMetaMTAMail();
        $template->setRelatedPHID($commit->getPHID());
        $template->setSubject($subject);
        $template->setVarySubject($subject);
        $template->setBody($body);
        $template->setThreadID($thread_id, $is_new = true);
        $template->addHeader('Thread-Topic', $thread_topic);
        $template->setIsBulk(true);
        $template->addHeader('X-Herald-Rules', $xscript->getXHeraldRulesHeader());
        if ($author_phid) {
            $template->setFrom($author_phid);
        }
        $mails = $reply_handler->multiplexMail($template, id(new PhabricatorObjectHandleData($email_phids))->loadHandles(), array());
        foreach ($mails as $mail) {
            $mail->saveAndSend();
        }
    }