protected function processReceivedObjectMail(PhabricatorMetaMTAReceivedMail $mail, PhabricatorLiskDAO $object, PhabricatorUser $sender)
 {
     $handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit($object);
     $handler->setActor($sender);
     $handler->setExcludeMailRecipientPHIDs($mail->loadExcludeMailRecipientPHIDs());
     $handler->processEmail($mail);
 }
 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 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();
 }
    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();
        }
    }