public function collectGarbage()
 {
     $ttl = phutil_units('90 days in seconds');
     $table = new PhabricatorMetaMTAMail();
     $conn_w = $table->establishConnection('w');
     queryfx($conn_w, 'DELETE FROM %T WHERE dateCreated < %d LIMIT 100', $table->getTableName(), time() - $ttl);
     return $conn_w->getAffectedRows() == 100;
 }
 public final function multiplexMail(PhabricatorMetaMTAMail $mail_template, array $to_handles, array $cc_handles)
 {
     $result = array();
     // If private replies are not supported, simply send one email to all
     // recipients and CCs. This covers cases where we have no reply handler,
     // or we have a public reply handler.
     if (!$this->supportsPrivateReplies()) {
         $mail = clone $mail_template;
         $mail->addTos(mpull($to_handles, 'getPHID'));
         $mail->addCCs(mpull($cc_handles, 'getPHID'));
         if ($this->supportsPublicReplies()) {
             $reply_to = $this->getPublicReplyHandlerEmailAddress();
             $mail->setReplyTo($reply_to);
         }
         $result[] = $mail;
         return $result;
     }
     // Merge all the recipients together. TODO: We could keep the CCs as real
     // CCs and send to a "*****@*****.**" type address, but keep it simple
     // for now.
     $recipients = mpull($to_handles, null, 'getPHID') + mpull($cc_handles, null, 'getPHID');
     // This grouping is just so we can use the public reply-to for any
     // recipients without a private reply-to, e.g. mailing lists.
     $groups = array();
     foreach ($recipients as $recipient) {
         $private = $this->getPrivateReplyHandlerEmailAddress($recipient);
         $groups[$private][] = $recipient;
     }
     // When multiplexing mail, explicitly include To/Cc information in the
     // message body and headers.
     $add_headers = array();
     $body = $mail_template->getBody();
     $body .= "\n";
     if ($to_handles) {
         $body .= "To: " . implode(', ', mpull($to_handles, 'getName')) . "\n";
         $add_headers['X-Phabricator-To'] = $this->formatPHIDList($to_handles);
     }
     if ($cc_handles) {
         $body .= "Cc: " . implode(', ', mpull($cc_handles, 'getName')) . "\n";
         $add_headers['X-Phabricator-Cc'] = $this->formatPHIDList($cc_handles);
     }
     foreach ($groups as $reply_to => $group) {
         $mail = clone $mail_template;
         $mail->addTos(mpull($group, 'getPHID'));
         $mail->setBody($body);
         foreach ($add_headers as $header => $value) {
             $mail->addHeader($header, $value);
         }
         if (!$reply_to && $this->supportsPublicReplies()) {
             $reply_to = $this->getPublicReplyHandlerEmailAddress();
         }
         if ($reply_to) {
             $mail->setReplyTo($reply_to);
         }
         $result[] = $mail;
     }
     return $result;
 }
 public function send()
 {
     $to_phids = $this->getToPHIDs();
     if (!$to_phids) {
         throw new Exception('No "To:" users provided!');
     }
     $cc_phids = $this->getCCPHIDs();
     $subject = $this->buildSubject();
     $body = $this->buildBody();
     $template = new PhabricatorMetaMTAMail();
     $actor_handle = $this->getActorHandle();
     $reply_handler = $this->getReplyHandler();
     if ($actor_handle) {
         $template->setFrom($actor_handle->getPHID());
     }
     $template->setSubject($subject)->setBody($body)->setIsHTML($this->shouldMarkMailAsHTML())->setParentMessageID($this->parentMessageID)->addHeader('Thread-Topic', $this->getRevision()->getTitle());
     $template->setThreadID($this->getThreadID(), $this->isFirstMailAboutRevision());
     if ($this->heraldRulesHeader) {
         $template->addHeader('X-Herald-Rules', $this->heraldRulesHeader);
     }
     $template->setRelatedPHID($this->getRevision()->getPHID());
     $phids = array();
     foreach ($to_phids as $phid) {
         $phids[$phid] = true;
     }
     foreach ($cc_phids as $phid) {
         $phids[$phid] = true;
     }
     $phids = array_keys($phids);
     $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
     $mails = $reply_handler->multiplexMail($template, array_select_keys($handles, $to_phids), array_select_keys($handles, $cc_phids));
     foreach ($mails as $mail) {
         $mail->saveAndSend();
     }
 }
 private function runThreadIDHeadersWithConfiguration($supports_message_id, $is_first_mail)
 {
     $mailer = new PhabricatorMailImplementationTestAdapter(array('supportsMessageIDHeader' => $supports_message_id));
     $thread_id = '<*****@*****.**>';
     $mail = new PhabricatorMetaMTAMail();
     $mail->setThreadID($thread_id, $is_first_mail);
     $mail->sendNow($force = true, $mailer);
     $guts = $mailer->getGuts();
     $dict = ipull($guts['headers'], 1, 0);
     if ($is_first_mail && $supports_message_id) {
         $expect_message_id = true;
         $expect_in_reply_to = false;
         $expect_references = false;
     } else {
         $expect_message_id = false;
         $expect_in_reply_to = true;
         $expect_references = true;
     }
     $case = "<message-id = " . ($supports_message_id ? 'Y' : 'N') . ", " . "first = " . ($is_first_mail ? 'Y' : 'N') . ">";
     $this->assertEqual(true, isset($dict['Thread-Index']), "Expect Thread-Index header for case {$case}.");
     $this->assertEqual($expect_message_id, isset($dict['Message-ID']), "Expectation about existence of Message-ID header for case {$case}.");
     $this->assertEqual($expect_in_reply_to, isset($dict['In-Reply-To']), "Expectation about existence of In-Reply-To header for case {$case}.");
     $this->assertEqual($expect_references, isset($dict['References']), "Expectation about existence of References header for case {$case}.");
 }
 public function execute(PhutilArgumentParser $args)
 {
     $console = PhutilConsole::getConsole();
     $viewer = $this->getViewer();
     $mails = id(new PhabricatorMetaMTAMail())->loadAllWhere('1 = 1 ORDER BY id DESC LIMIT %d', $args->getArg('limit'));
     if (!$mails) {
         $console->writeErr("%s\n", pht('No sent mail.'));
         return 0;
     }
     $table = id(new PhutilConsoleTable())->setShowHeader(false)->addColumn('id', array('title' => 'ID'))->addColumn('status', array('title' => 'Status'))->addColumn('subject', array('title' => 'Subject'));
     foreach (array_reverse($mails) as $mail) {
         $status = $mail->getStatus();
         $table->addRow(array('id' => $mail->getID(), 'status' => PhabricatorMetaMTAMail::getReadableStatus($status), 'subject' => $mail->getSubject()));
     }
     $table->draw();
     return 0;
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $mail = id(new PhabricatorMetaMTAMail())->load($this->id);
     if (!$mail) {
         return new Aphront404Response();
     }
     $status = PhabricatorMetaMTAMail::getReadableStatus($mail->getStatus());
     $form = new AphrontFormView();
     $form->setUser($request->getUser());
     $form->appendChild(id(new AphrontFormStaticControl())->setLabel('Subject')->setValue($mail->getSubject()))->appendChild(id(new AphrontFormStaticControl())->setLabel('Created')->setValue(phabricator_datetime($mail->getDateCreated(), $user)))->appendChild(id(new AphrontFormStaticControl())->setLabel('Status')->setValue($status))->appendChild(id(new AphrontFormStaticControl())->setLabel('Retry Count')->setValue($mail->getRetryCount()))->appendChild(id(new AphrontFormStaticControl())->setLabel('Message')->setValue($mail->getMessage()))->appendChild(id(new AphrontFormStaticControl())->setLabel('Related PHID')->setValue($mail->getRelatedPHID()))->appendChild(id(new AphrontFormSubmitControl())->addCancelButton($this->getApplicationURI(), 'Done'));
     $panel = new AphrontPanelView();
     $panel->setHeader('View Email');
     $panel->appendChild($form);
     $panel->setWidth(AphrontPanelView::WIDTH_WIDE);
     return $this->buildApplicationPage($panel, array('title' => 'View Mail'));
 }
 public function send()
 {
     $to_phids = $this->getToPHIDs();
     if (!$to_phids) {
         throw new Exception('No "To:" users provided!');
     }
     $cc_phids = $this->getCCPHIDs();
     $subject = $this->buildSubject();
     $vary_subject = $this->buildVarySubject();
     $body = $this->buildBody();
     $attachments = $this->buildAttachments();
     $template = new PhabricatorMetaMTAMail();
     $actor_handle = $this->getActorHandle();
     $reply_handler = $this->getReplyHandler();
     if ($actor_handle) {
         $template->setFrom($actor_handle->getPHID());
     }
     $template->setSubject($subject)->setVarySubject($vary_subject)->setBody($body)->setIsHTML($this->shouldMarkMailAsHTML())->setParentMessageID($this->parentMessageID)->addHeader('Thread-Topic', $this->getRevision()->getTitle());
     $template->setAttachments($attachments);
     $template->setThreadID($this->getThreadID(), $this->isFirstMailAboutRevision());
     if ($this->heraldRulesHeader) {
         $template->addHeader('X-Herald-Rules', $this->heraldRulesHeader);
     }
     $revision = $this->revision;
     if ($revision) {
         if ($revision->getAuthorPHID()) {
             $template->addHeader('X-Differential-Author', '<' . $revision->getAuthorPHID() . '>');
         }
         if ($revision->getReviewers()) {
             $template->addHeader('X-Differential-Reviewers', '<' . implode('>, <', $revision->getReviewers()) . '>');
         }
         if ($revision->getCCPHIDs()) {
             $template->addHeader('X-Differential-CCs', '<' . implode('>, <', $revision->getCCPHIDs()) . '>');
             // Determine explicit CCs (those added by humans) and put them in a
             // header so users can differentiate between Herald CCs and human CCs.
             $relation_subscribed = DifferentialRevision::RELATION_SUBSCRIBED;
             $raw = $revision->getRawRelations($relation_subscribed);
             $reason_phids = ipull($raw, 'reasonPHID');
             $reason_handles = id(new PhabricatorObjectHandleData($reason_phids))->loadHandles();
             $explicit_cc = array();
             foreach ($raw as $relation) {
                 if (!$relation['reasonPHID']) {
                     continue;
                 }
                 $type = $reason_handles[$relation['reasonPHID']]->getType();
                 if ($type == PhabricatorPHIDConstants::PHID_TYPE_USER) {
                     $explicit_cc[] = $relation['objectPHID'];
                 }
             }
             if ($explicit_cc) {
                 $template->addHeader('X-Differential-Explicit-CCs', '<' . implode('>, <', $explicit_cc) . '>');
             }
         }
     }
     $template->setIsBulk(true);
     $template->setRelatedPHID($this->getRevision()->getPHID());
     $mailtags = $this->getMailTags();
     if ($mailtags) {
         $template->setMailTags($mailtags);
     }
     $phids = array();
     foreach ($to_phids as $phid) {
         $phids[$phid] = true;
     }
     foreach ($cc_phids as $phid) {
         $phids[$phid] = true;
     }
     $phids = array_keys($phids);
     $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
     $event = new PhabricatorEvent(PhabricatorEventType::TYPE_DIFFERENTIAL_WILLSENDMAIL, array('mail' => $template));
     PhutilEventEngine::dispatchEvent($event);
     $template = $event->getValue('mail');
     $mails = $reply_handler->multiplexMail($template, array_select_keys($handles, $to_phids), array_select_keys($handles, $cc_phids));
     foreach ($mails as $mail) {
         $mail->saveAndSend();
     }
 }
 public function willSendMail(PhabricatorMetaMTAMail $mail)
 {
     $viewer = $this->getViewer();
     $mail->addPHIDHeaders('X-Phabricator-To', $this->rawToPHIDs);
     $mail->addPHIDHeaders('X-Phabricator-Cc', $this->rawCCPHIDs);
     $to_handles = $viewer->loadHandles($this->rawToPHIDs);
     $cc_handles = $viewer->loadHandles($this->rawCCPHIDs);
     $body = $mail->getBody();
     $body .= "\n";
     $body .= $this->getRecipientsSummary($to_handles, $cc_handles);
     $mail->setBody($body);
     $html_body = $mail->getHTMLBody();
     if (strlen($html_body)) {
         $html_body .= hsprintf('%s', $this->getRecipientsSummaryHTML($to_handles, $cc_handles));
     }
     $mail->setHTMLBody($html_body);
     $reply_to = $this->getReplyTo();
     if ($reply_to) {
         $mail->setReplyTo($reply_to);
     }
     $to = array_keys($this->getToMap());
     if ($to) {
         $mail->addTos($to);
     }
     $cc = array_keys($this->getCCMap());
     if ($cc) {
         $mail->addCCs($cc);
     }
     return $mail;
 }
 public function send()
 {
     $to_phids = $this->getToPHIDs();
     if (!$to_phids) {
         throw new Exception('No "To:" users provided!');
     }
     $cc_phids = $this->getCCPHIDs();
     $attachments = $this->buildAttachments();
     $template = new PhabricatorMetaMTAMail();
     $actor_handle = $this->getActorHandle();
     $reply_handler = $this->getReplyHandler();
     if ($actor_handle) {
         $template->setFrom($actor_handle->getPHID());
     }
     $template->setIsHTML($this->shouldMarkMailAsHTML())->setParentMessageID($this->parentMessageID)->addHeader('Thread-Topic', $this->getThreadTopic());
     $template->setAttachments($attachments);
     $template->setThreadID($this->getThreadID(), $this->isFirstMailAboutRevision());
     if ($this->heraldRulesHeader) {
         $template->addHeader('X-Herald-Rules', $this->heraldRulesHeader);
     }
     $revision = $this->revision;
     if ($revision) {
         if ($revision->getAuthorPHID()) {
             $template->addHeader('X-Differential-Author', '<' . $revision->getAuthorPHID() . '>');
         }
         $reviewer_phids = $revision->getReviewers();
         if ($reviewer_phids) {
             // Add several headers to support e-mail clients which are not able to
             // create rules using regular expressions or wildcards (namely Outlook).
             $template->addPHIDHeaders('X-Differential-Reviewer', $reviewer_phids);
             // Add it also as a list to allow matching of the first reviewer and
             // also for backwards compatibility.
             $template->addHeader('X-Differential-Reviewers', '<' . implode('>, <', $reviewer_phids) . '>');
         }
         if ($cc_phids) {
             $template->addPHIDHeaders('X-Differential-CC', $cc_phids);
             $template->addHeader('X-Differential-CCs', '<' . implode('>, <', $cc_phids) . '>');
             // Determine explicit CCs (those added by humans) and put them in a
             // header so users can differentiate between Herald CCs and human CCs.
             $relation_subscribed = DifferentialRevision::RELATION_SUBSCRIBED;
             $raw = $revision->getRawRelations($relation_subscribed);
             $reason_phids = ipull($raw, 'reasonPHID');
             $reason_handles = id(new PhabricatorObjectHandleData($reason_phids))->loadHandles();
             $explicit_cc = array();
             foreach ($raw as $relation) {
                 if (!$relation['reasonPHID']) {
                     continue;
                 }
                 $type = $reason_handles[$relation['reasonPHID']]->getType();
                 if ($type == PhabricatorPHIDConstants::PHID_TYPE_USER) {
                     $explicit_cc[] = $relation['objectPHID'];
                 }
             }
             if ($explicit_cc) {
                 $template->addPHIDHeaders('X-Differential-Explicit-CC', $explicit_cc);
                 $template->addHeader('X-Differential-Explicit-CCs', '<' . implode('>, <', $explicit_cc) . '>');
             }
         }
     }
     $template->setIsBulk(true);
     $template->setRelatedPHID($this->getRevision()->getPHID());
     $mailtags = $this->getMailTags();
     if ($mailtags) {
         $template->setMailTags($mailtags);
     }
     $phids = array();
     foreach ($to_phids as $phid) {
         $phids[$phid] = true;
     }
     foreach ($cc_phids as $phid) {
         $phids[$phid] = true;
     }
     $phids = array_keys($phids);
     $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
     $objects = id(new PhabricatorObjectHandleData($phids))->loadObjects();
     $to_handles = array_select_keys($handles, $to_phids);
     $cc_handles = array_select_keys($handles, $cc_phids);
     $this->prepareBody();
     $mails = $reply_handler->multiplexMail($template, $to_handles, $cc_handles);
     $original_translator = PhutilTranslator::getInstance();
     if (!PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
         $translation = PhabricatorEnv::newObjectFromConfig('translation.provider');
         $translator = id(new PhutilTranslator())->setLanguage($translation->getLanguage())->addTranslations($translation->getTranslations());
     }
     try {
         foreach ($mails as $mail) {
             if (PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
                 $translation = newv($mail->getTranslation($objects), array());
                 $translator = id(new PhutilTranslator())->setLanguage($translation->getLanguage())->addTranslations($translation->getTranslations());
                 PhutilTranslator::setInstance($translator);
             }
             $body = $this->buildBody() . "\n" . $reply_handler->getRecipientsSummary($to_handles, $cc_handles);
             $mail->setSubject($this->renderSubject())->setSubjectPrefix($this->getSubjectPrefix())->setVarySubjectPrefix($this->renderVaryPrefix())->setBody($body);
             $event = new PhabricatorEvent(PhabricatorEventType::TYPE_DIFFERENTIAL_WILLSENDMAIL, array('mail' => $mail));
             PhutilEventEngine::dispatchEvent($event);
             $mail = $event->getValue('mail');
             $mail->saveAndSend();
         }
     } catch (Exception $ex) {
         PhutilTranslator::setInstance($original_translator);
         throw $ex;
     }
     PhutilTranslator::setInstance($original_translator);
 }
 private function buildPatch(PhabricatorMetaMTAMail $template, PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit)
 {
     $attach_key = 'metamta.diffusion.attach-patches';
     $inline_key = 'metamta.diffusion.inline-patches';
     $attach_patches = PhabricatorEnv::getEnvConfig($attach_key);
     $inline_patches = PhabricatorEnv::getEnvConfig($inline_key);
     if (!$attach_patches && !$inline_patches) {
         return;
     }
     $encoding = $repository->getDetail('encoding', 'UTF-8');
     $result = null;
     $patch_error = null;
     try {
         $raw_patch = $this->loadRawPatchText($repository, $commit);
         if ($attach_patches) {
             $commit_name = $repository->formatCommitName($commit->getCommitIdentifier());
             $template->addAttachment(new PhabricatorMetaMTAAttachment($raw_patch, $commit_name . '.patch', 'text/x-patch; charset=' . $encoding));
         }
     } catch (Exception $ex) {
         phlog($ex);
         $patch_error = 'Unable to generate: ' . $ex->getMessage();
     }
     if ($patch_error) {
         $result = $patch_error;
     } else {
         if ($inline_patches) {
             $len = substr_count($raw_patch, "\n");
             if ($len <= $inline_patches) {
                 // We send email as utf8, so we need to convert the text to utf8 if
                 // we can.
                 if ($encoding) {
                     $raw_patch = phutil_utf8_convert($raw_patch, 'UTF-8', $encoding);
                 }
                 $result = phutil_utf8ize($raw_patch);
             }
         }
     }
     if ($result) {
         $result = "PATCH\n\n{$result}\n";
     }
     return $result;
 }
 public final function multiplexMail(PhabricatorMetaMTAMail $mail_template, array $to_handles, array $cc_handles)
 {
     assert_instances_of($to_handles, 'PhabricatorObjectHandle');
     assert_instances_of($cc_handles, 'PhabricatorObjectHandle');
     $result = array();
     // If MetaMTA is configured to always multiplex, skip the single-email
     // case.
     if (!PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
         // If private replies are not supported, simply send one email to all
         // recipients and CCs. This covers cases where we have no reply handler,
         // or we have a public reply handler.
         if (!$this->supportsPrivateReplies()) {
             $mail = clone $mail_template;
             $mail->addTos(mpull($to_handles, 'getPHID'));
             $mail->addCCs(mpull($cc_handles, 'getPHID'));
             if ($this->supportsPublicReplies()) {
                 $reply_to = $this->getPublicReplyHandlerEmailAddress();
                 $mail->setReplyTo($reply_to);
             }
             $result[] = $mail;
             return $result;
         }
     }
     $tos = mpull($to_handles, null, 'getPHID');
     $ccs = mpull($cc_handles, null, 'getPHID');
     // Merge all the recipients together. TODO: We could keep the CCs as real
     // CCs and send to a "*****@*****.**" type address, but keep it simple
     // for now.
     $recipients = $tos + $ccs;
     // When multiplexing mail, explicitly include To/Cc information in the
     // message body and headers.
     $mail_template = clone $mail_template;
     $mail_template->addPHIDHeaders('X-Phabricator-To', array_keys($tos));
     $mail_template->addPHIDHeaders('X-Phabricator-Cc', array_keys($ccs));
     $body = $mail_template->getBody();
     $body .= "\n";
     $body .= $this->getRecipientsSummary($to_handles, $cc_handles);
     foreach ($recipients as $phid => $recipient) {
         $mail = clone $mail_template;
         if (isset($to_handles[$phid])) {
             $mail->addTos(array($phid));
         } else {
             if (isset($cc_handles[$phid])) {
                 $mail->addCCs(array($phid));
             } else {
                 // not good - they should be a to or a cc
                 continue;
             }
         }
         $mail->setBody($body);
         $reply_to = null;
         if (!$reply_to && $this->supportsPrivateReplies()) {
             $reply_to = $this->getPrivateReplyHandlerEmailAddress($recipient);
         }
         if (!$reply_to && $this->supportsPublicReplies()) {
             $reply_to = $this->getPublicReplyHandlerEmailAddress();
         }
         if ($reply_to) {
             $mail->setReplyTo($reply_to);
         }
         $result[] = $mail;
     }
     return $result;
 }
 public function processRequest()
 {
     // Get a page of mails together with pager.
     $request = $this->getRequest();
     $user = $request->getUser();
     $offset = $request->getInt('offset', 0);
     $related_phid = $request->getStr('phid');
     $status = $request->getStr('status');
     $pager = new AphrontPagerView();
     $pager->setOffset($offset);
     $pager->setURI($request->getRequestURI(), 'offset');
     $mail = new PhabricatorMetaMTAMail();
     $conn_r = $mail->establishConnection('r');
     $wheres = array();
     if ($status) {
         $wheres[] = qsprintf($conn_r, 'status = %s', $status);
     }
     if ($related_phid) {
         $wheres[] = qsprintf($conn_r, 'relatedPHID = %s', $related_phid);
     }
     if (count($wheres)) {
         $where_clause = 'WHERE ' . implode($wheres, ' AND ');
     } else {
         $where_clause = 'WHERE 1 = 1';
     }
     $data = queryfx_all($conn_r, 'SELECT * FROM %T
     %Q
     ORDER BY id DESC
     LIMIT %d, %d', $mail->getTableName(), $where_clause, $pager->getOffset(), $pager->getPageSize() + 1);
     $data = $pager->sliceResults($data);
     $mails = $mail->loadAllFromArray($data);
     // Render the details table.
     $rows = array();
     foreach ($mails as $mail) {
         $rows[] = array(PhabricatorMetaMTAMail::getReadableStatus($mail->getStatus()), $mail->getRetryCount(), $mail->getNextRetry() - time() . ' s', phabricator_datetime($mail->getDateCreated(), $user), time() - $mail->getDateModified() . ' s', phutil_escape_html($mail->getSubject()), phutil_render_tag('a', array('class' => 'button small grey', 'href' => '/mail/view/' . $mail->getID() . '/'), 'View'));
     }
     $table = new AphrontTableView($rows);
     $table->setHeaders(array('Status', 'Retry', 'Next', 'Created', 'Updated', 'Subject', ''));
     $table->setColumnClasses(array(null, null, null, null, null, 'wide', 'action'));
     // Render the whole page.
     $panel = new AphrontPanelView();
     $panel->appendChild($table);
     $panel->setHeader('MetaMTA Messages');
     if ($user->getIsAdmin()) {
         $panel->setCreateButton('Send New Test Message', '/mail/send/');
     }
     $panel->appendChild($pager);
     return $this->buildStandardPageResponse($panel, array('title' => 'MetaMTA', 'tab' => 'queue'));
 }
 public function isUserPanel()
 {
     return PhabricatorMetaMTAMail::shouldMultiplexAllMail();
 }
    public function processRequest()
    {
        $request = $this->getRequest();
        if (!PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) {
            return new Aphront400Response();
        }
        $e_email = true;
        $e_captcha = true;
        $errors = array();
        if ($request->isFormPost()) {
            $e_email = null;
            $e_captcha = 'Again';
            $captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request);
            if (!$captcha_ok) {
                $errors[] = "Captcha response is incorrect, try again.";
                $e_captcha = 'Invalid';
            }
            $email = $request->getStr('email');
            if (!strlen($email)) {
                $errors[] = "You must provide an email address.";
                $e_email = 'Required';
            }
            if (!$errors) {
                // NOTE: Don't validate the email unless the captcha is good; this makes
                // it expensive to fish for valid email addresses while giving the user
                // a better error if they goof their email.
                $target_user = id(new PhabricatorUser())->loadOneWhere('email = %s', $email);
                if (!$target_user) {
                    $errors[] = "There is no account associated with that email address.";
                    $e_email = "Invalid";
                }
                if (!$errors) {
                    $uri = $target_user->getEmailLoginURI();
                    $body = <<<EOBODY
Condolences on forgetting your password. You can use this link to reset it:

  {$uri}

After you set a new password, consider writing it down on a sticky note and
attaching it to your monitor so you don't forget again! Choosing a very short,
easy-to-remember password like "cat" or "1234" might also help.

Best Wishes,
Phabricator

EOBODY;
                    $mail = new PhabricatorMetaMTAMail();
                    $mail->setSubject('[Phabricator] Password Reset');
                    $mail->setFrom($target_user->getPHID());
                    $mail->addTos(array($target_user->getPHID()));
                    $mail->setBody($body);
                    $mail->saveAndSend();
                    $view = new AphrontRequestFailureView();
                    $view->setHeader('Check Your Email');
                    $view->appendChild('<p>An email has been sent with a link you can use to login.</p>');
                    return $this->buildStandardPageResponse($view, array('title' => 'Email Sent'));
                }
            }
        }
        $email_auth = new AphrontFormView();
        $email_auth->setAction('/login/email/')->setUser($request->getUser())->appendChild(id(new AphrontFormTextControl())->setLabel('Email')->setName('email')->setValue($request->getStr('email'))->setError($e_email))->appendChild(id(new AphrontFormRecaptchaControl())->setLabel('Captcha')->setError($e_captcha))->appendChild(id(new AphrontFormSubmitControl())->setValue('Send Email'));
        $error_view = null;
        if ($errors) {
            $error_view = new AphrontErrorView();
            $error_view->setTitle('Login Error');
            $error_view->setErrors($errors);
        }
        $panel = new AphrontPanelView();
        $panel->setWidth(AphrontPanelView::WIDTH_FORM);
        $panel->appendChild('<h1>Forgot Password / Email Login</h1>');
        $panel->appendChild($email_auth);
        return $this->buildStandardPageResponse(array($error_view, $panel), array('title' => 'Create New Account'));
    }
    public function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit)
    {
        if ($repository->getDetail('herald-disabled')) {
            return;
        }
        $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $commit->getID());
        $rules = HeraldRule::loadAllByContentTypeWithFullData(HeraldContentTypeConfig::CONTENT_TYPE_COMMIT);
        $adapter = new HeraldCommitAdapter($repository, $commit, $data);
        $engine = new HeraldEngine();
        $effects = $engine->applyRules($rules, $adapter);
        $engine->applyEffects($effects, $adapter);
        $email_phids = $adapter->getEmailPHIDs();
        if (!$email_phids) {
            return;
        }
        $xscript = $engine->getTranscript();
        $commit_name = $adapter->getHeraldName();
        $revision = $adapter->loadDifferentialRevision();
        $name = null;
        if ($revision) {
            $name = ' ' . $revision->getTitle();
        }
        $author_phid = $data->getCommitDetail('authorPHID');
        $reviewer_phid = $data->getCommitDetail('reviewerPHID');
        $phids = array_filter(array($author_phid, $reviewer_phid));
        $handles = array();
        if ($phids) {
            $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
        }
        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();
        $details = PhabricatorEnv::getProductionURI('/' . $commit_name);
        $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 . '/');
        $body = <<<EOBODY
DESCRIPTION
{$description}

DETAILS
  {$details}

DIFFERENTIAL REVISION
  {$differential}

AFFECTED FILES
  {$files}

MANAGE HERALD COMMIT RULES
  {$manage_uri}

WHY DID I GET THIS EMAIL?
  {$why_uri}

EOBODY;
        $subject = "[Herald/Commit] {$commit_name} ({$who}){$name}";
        $mailer = new PhabricatorMetaMTAMail();
        $mailer->setRelatedPHID($commit->getPHID());
        $mailer->addTos($email_phids);
        $mailer->setSubject($subject);
        $mailer->setBody($body);
        $mailer->addHeader('X-Herald-Rules', $xscript->getXHeraldRulesHeader());
        if ($author_phid) {
            $mailer->setFrom($author_phid);
        }
        $mailer->saveAndSend();
    }
 public final function multiplexMail(PhabricatorMetaMTAMail $mail_template, array $to_handles, array $cc_handles)
 {
     assert_instances_of($to_handles, 'PhabricatorObjectHandle');
     assert_instances_of($cc_handles, 'PhabricatorObjectHandle');
     $result = array();
     // If MetaMTA is configured to always multiplex, skip the single-email
     // case.
     if (!PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
         // If private replies are not supported, simply send one email to all
         // recipients and CCs. This covers cases where we have no reply handler,
         // or we have a public reply handler.
         if (!$this->supportsPrivateReplies()) {
             $mail = clone $mail_template;
             $mail->addTos(mpull($to_handles, 'getPHID'));
             $mail->addCCs(mpull($cc_handles, 'getPHID'));
             if ($this->supportsPublicReplies()) {
                 $reply_to = $this->getPublicReplyHandlerEmailAddress();
                 $mail->setReplyTo($reply_to);
             }
             $result[] = $mail;
             return $result;
         }
     }
     // Merge all the recipients together. TODO: We could keep the CCs as real
     // CCs and send to a "*****@*****.**" type address, but keep it simple
     // for now.
     $recipients = mpull($to_handles, null, 'getPHID') + mpull($cc_handles, null, 'getPHID');
     // When multiplexing mail, explicitly include To/Cc information in the
     // message body and headers.
     $add_headers = array();
     $body = $mail_template->getBody();
     $body .= "\n";
     if ($to_handles) {
         $body .= "To: " . implode(', ', mpull($to_handles, 'getName')) . "\n";
         $add_headers['X-Phabricator-To'] = $this->formatPHIDList($to_handles);
     }
     if ($cc_handles) {
         $body .= "Cc: " . implode(', ', mpull($cc_handles, 'getName')) . "\n";
         $add_headers['X-Phabricator-Cc'] = $this->formatPHIDList($cc_handles);
     }
     foreach ($recipients as $recipient) {
         $mail = clone $mail_template;
         $mail->addTos(array($recipient->getPHID()));
         $mail->setBody($body);
         foreach ($add_headers as $header => $value) {
             $mail->addHeader($header, $value);
         }
         $reply_to = null;
         if (!$reply_to && $this->supportsPrivateReplies()) {
             $reply_to = $this->getPrivateReplyHandlerEmailAddress($recipient);
         }
         if (!$reply_to && $this->supportsPublicReplies()) {
             $reply_to = $this->getPublicReplyHandlerEmailAddress();
         }
         if ($reply_to) {
             $mail->setReplyTo($reply_to);
         }
         $result[] = $mail;
     }
     return $result;
 }
<?php

$table = new PhabricatorMetaMTAMail();
$conn_w = $table->establishConnection('w');
echo pht('Assigning PHIDs to mails...') . "\n";
foreach (new LiskMigrationIterator($table) as $mail) {
    $id = $mail->getID();
    echo pht('Updating mail %d...', $id) . "\n";
    if ($mail->getPHID()) {
        continue;
    }
    queryfx($conn_w, 'UPDATE %T SET phid = %s WHERE id = %d', $table->getTableName(), $table->generatePHID(), $id);
}
echo pht('Done.') . "\n";
 private function attachPatch(PhabricatorMetaMTAMail $template, PhabricatorRepositoryCommit $commit)
 {
     if (!$this->getRawPatch()) {
         return;
     }
     $attach_key = 'metamta.diffusion.attach-patches';
     $attach_patches = PhabricatorEnv::getEnvConfig($attach_key);
     if (!$attach_patches) {
         return;
     }
     $repository = $commit->getRepository();
     $encoding = $repository->getDetail('encoding', 'UTF-8');
     $raw_patch = $this->getRawPatch();
     $commit_name = $repository->formatCommitName($commit->getCommitIdentifier());
     $template->addAttachment(new PhabricatorMetaMTAAttachment($raw_patch, $commit_name . '.patch', 'text/x-patch; charset=' . $encoding));
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     if ($request->isFormPost()) {
         $mail = new PhabricatorMetaMTAMail();
         $mail->addTos($request->getArr('to'));
         $mail->addCCs($request->getArr('cc'));
         $mail->setSubject($request->getStr('subject'));
         $mail->setBody($request->getStr('body'));
         $files = $request->getArr('files');
         if ($files) {
             foreach ($files as $phid) {
                 $file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $phid);
                 $mail->addAttachment(new PhabricatorMetaMTAAttachment($file->loadFileData(), $file->getName(), $file->getMimeType()));
             }
         }
         $mail->setFrom($request->getUser()->getPHID());
         $mail->setSimulatedFailureCount($request->getInt('failures'));
         $mail->setIsHTML($request->getInt('html'));
         $mail->setIsBulk($request->getInt('bulk'));
         $mail->save();
         if ($request->getInt('immediately')) {
             $mail->sendNow();
         }
         return id(new AphrontRedirectResponse())->setURI('/mail/view/' . $mail->getID() . '/');
     }
     $failure_caption = "Enter a number to simulate that many consecutive send failures before " . "really attempting to deliver via the underlying MTA.";
     $doclink_href = PhabricatorEnv::getDoclink('article/Configuring_Outbound_Email.html');
     $doclink = phutil_render_tag('a', array('href' => $doclink_href, 'target' => '_blank'), 'Configuring Outbound Email');
     $instructions = '<p class="aphront-form-instructions">This form will send a normal ' . 'email using the settings you have configured for Phabricator. For more ' . 'information, see ' . $doclink . '.</p>';
     $adapter = PhabricatorEnv::getEnvConfig('metamta.mail-adapter');
     $warning = null;
     if ($adapter == 'PhabricatorMailImplementationTestAdapter') {
         $warning = new AphrontErrorView();
         $warning->setTitle('Email is Disabled');
         $warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
         $warning->appendChild('<p>This installation of Phabricator is currently set to use ' . '<tt>PhabricatorMailImplementationTestAdapter</tt> to deliver ' . 'outbound email. This completely disables outbound email! All ' . 'outbound email will be thrown in a deep, dark hole until you ' . 'configure a real adapter.</p>');
     }
     $panel_id = celerity_generate_unique_node_id();
     $form = new AphrontFormView();
     $form->setUser($request->getUser());
     $form->setAction('/mail/send/');
     $form->appendChild($instructions)->appendChild(id(new AphrontFormStaticControl())->setLabel('Configured Adapter')->setValue($adapter))->appendChild(id(new AphrontFormTokenizerControl())->setLabel('To')->setName('to')->setDatasource('/typeahead/common/mailable/'))->appendChild(id(new AphrontFormTokenizerControl())->setLabel('CC')->setName('cc')->setDatasource('/typeahead/common/mailable/'))->appendChild(id(new AphrontFormTextControl())->setLabel('Subject')->setName('subject'))->appendChild(id(new AphrontFormTextAreaControl())->setLabel('Body')->setName('body'))->appendChild(id(new AphrontFormDragAndDropUploadControl())->setLabel('Attach Files')->setName('files')->setDragAndDropTarget($panel_id)->setActivatedClass('aphront-panel-view-drag-and-drop'))->appendChild(id(new AphrontFormTextControl())->setLabel('Simulate Failures')->setName('failures')->setCaption($failure_caption))->appendChild(id(new AphrontFormCheckboxControl())->setLabel('HTML')->addCheckbox('html', '1', 'Send as HTML email.'))->appendChild(id(new AphrontFormCheckboxControl())->setLabel('Bulk')->addCheckbox('bulk', '1', 'Send with bulk email headers.'))->appendChild(id(new AphrontFormCheckboxControl())->setLabel('Send Now')->addCheckbox('immediately', '1', 'Send immediately, not via MetaMTA background script.'))->appendChild(id(new AphrontFormSubmitControl())->setValue('Send Mail'));
     $panel = new AphrontPanelView();
     $panel->setHeader('Send Email');
     $panel->appendChild($form);
     $panel->setID($panel_id);
     $panel->setWidth(AphrontPanelView::WIDTH_WIDE);
     return $this->buildStandardPageResponse(array($warning, $panel), array('title' => 'Send Mail'));
 }
 private function sendMailToSubscribers(array $subscribers, $old_content)
 {
     if (!$subscribers) {
         return;
     }
     $author_phid = $this->getActor()->getPHID();
     $document = $this->document;
     $content = $document->getContent();
     $slug_uri = PhrictionDocument::getSlugURI($document->getSlug());
     $diff_uri = new PhutilURI('/phriction/diff/' . $document->getID() . '/');
     $prod_uri = PhabricatorEnv::getProductionURI('');
     $vs_head = $diff_uri->alter('l', $old_content->getVersion())->alter('r', $content->getVersion());
     $old_title = $old_content->getTitle();
     $title = $content->getTitle();
     $name = $this->getChangeTypeDescription($content->getChangeType(), $title);
     $action = PhrictionChangeType::getChangeTypeLabel($content->getChangeType());
     $body = array($name);
     // Content may have changed, you never know
     if ($content->getChangeType() == PhrictionChangeType::CHANGE_EDIT) {
         if ($old_title != $title) {
             $body[] = pht('Title was changed from "%s" to "%s"', $old_title, $title);
         }
         $body[] = pht("Link to new version:\n%s", $prod_uri . $slug_uri . '?v=' . $content->getVersion());
         $body[] = pht("Link to diff:\n%s", $prod_uri . $vs_head);
     } else {
         if ($content->getChangeType() == PhrictionChangeType::CHANGE_MOVE_AWAY) {
             $target_document = id(new PhrictionDocument())->load($content->getChangeRef());
             $slug_uri = PhrictionDocument::getSlugURI($target_document->getSlug());
             $body[] = pht("Link to destination document:\n%s", $prod_uri . $slug_uri);
         }
     }
     $body = implode("\n\n", $body);
     $subject_prefix = $this->getMailSubjectPrefix();
     $mail = new PhabricatorMetaMTAMail();
     $mail->setSubject($name)->setSubjectPrefix($subject_prefix)->setVarySubjectPrefix('[' . $action . ']')->addHeader('Thread-Topic', $name)->setFrom($author_phid)->addTos($subscribers)->setBody($body)->setRelatedPHID($document->getPHID())->setIsBulk(true);
     $mail->saveAndSend();
 }
 private function buildMetadataProperties(PhabricatorMetaMTAMail $mail)
 {
     $viewer = $this->getViewer();
     $properties = id(new PHUIPropertyListView())->setUser($viewer);
     $properties->addProperty(pht('Message PHID'), $mail->getPHID());
     $details = $mail->getMessage();
     if (!strlen($details)) {
         $details = phutil_tag('em', array(), pht('None'));
     }
     $properties->addProperty(pht('Status Details'), $details);
     $actor_phid = $mail->getActorPHID();
     if ($actor_phid) {
         $actor_str = $viewer->renderHandle($actor_phid);
     } else {
         $actor_str = pht('Generated by Phabricator');
     }
     $properties->addProperty(pht('Actor'), $actor_str);
     $related_phid = $mail->getRelatedPHID();
     if ($related_phid) {
         $related = $viewer->renderHandle($mail->getRelatedPHID());
     } else {
         $related = phutil_tag('em', array(), pht('None'));
     }
     $properties->addProperty(pht('Related Object'), $related);
     return $properties;
 }
 public function processRequest(AphrontRequest $request)
 {
     $user = $request->getUser();
     $preferences = $user->loadPreferences();
     $pref_re_prefix = PhabricatorUserPreferences::PREFERENCE_RE_PREFIX;
     $pref_vary = PhabricatorUserPreferences::PREFERENCE_VARY_SUBJECT;
     $pref_no_self_mail = PhabricatorUserPreferences::PREFERENCE_NO_SELF_MAIL;
     $errors = array();
     if ($request->isFormPost()) {
         if (PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
             if ($request->getStr($pref_re_prefix) == 'default') {
                 $preferences->unsetPreference($pref_re_prefix);
             } else {
                 $preferences->setPreference($pref_re_prefix, $request->getBool($pref_re_prefix));
             }
             if ($request->getStr($pref_vary) == 'default') {
                 $preferences->unsetPreference($pref_vary);
             } else {
                 $preferences->setPreference($pref_vary, $request->getBool($pref_vary));
             }
         }
         $preferences->setPreference($pref_no_self_mail, $request->getStr($pref_no_self_mail));
         $new_tags = $request->getArr('mailtags');
         $mailtags = $preferences->getPreference('mailtags', array());
         foreach ($this->getMailTags() as $key => $label) {
             $mailtags[$key] = (bool) idx($new_tags, $key, false);
         }
         $preferences->setPreference('mailtags', $mailtags);
         $preferences->save();
         return id(new AphrontRedirectResponse())->setURI($this->getPanelURI('?saved=true'));
     }
     $notice = null;
     if (!$errors) {
         if ($request->getStr('saved')) {
             $notice = new AphrontErrorView();
             $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
             $notice->setTitle('Changes Saved');
             $notice->appendChild('<p>Your changes have been saved.</p>');
         }
     } else {
         $notice = new AphrontErrorView();
         $notice->setTitle('Form Errors');
         $notice->setErrors($errors);
     }
     $re_prefix_default = PhabricatorEnv::getEnvConfig('metamta.re-prefix') ? 'Enabled' : 'Disabled';
     $vary_default = PhabricatorEnv::getEnvConfig('metamta.vary-subjects') ? 'Vary' : 'Do Not Vary';
     $re_prefix_value = $preferences->getPreference($pref_re_prefix);
     if ($re_prefix_value === null) {
         $re_prefix_value = 'default';
     } else {
         $re_prefix_value = $re_prefix_value ? 'true' : 'false';
     }
     $vary_value = $preferences->getPreference($pref_vary);
     if ($vary_value === null) {
         $vary_value = 'default';
     } else {
         $vary_value = $vary_value ? 'true' : 'false';
     }
     $form = new AphrontFormView();
     $form->setUser($user)->appendChild(id(new AphrontFormSelectControl())->setLabel('Self Actions')->setName($pref_no_self_mail)->setOptions(array('0' => 'Send me an email when I take an action', '1' => 'Do not send me an email when I take an action'))->setCaption('You can disable email about your own actions.')->setValue($preferences->getPreference($pref_no_self_mail, 0)));
     if (PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
         $re_control = id(new AphrontFormSelectControl())->setName($pref_re_prefix)->setOptions(array('default' => 'Use Server Default (' . $re_prefix_default . ')', 'true' => 'Enable "Re:" prefix', 'false' => 'Disable "Re:" prefix'))->setValue($re_prefix_value);
         $vary_control = id(new AphrontFormSelectControl())->setName($pref_vary)->setOptions(array('default' => 'Use Server Default (' . $vary_default . ')', 'true' => 'Vary Subjects', 'false' => 'Do Not Vary Subjects'))->setValue($vary_value);
     } else {
         $re_control = id(new AphrontFormStaticControl())->setValue('Server Default (' . $re_prefix_default . ')');
         $vary_control = id(new AphrontFormStaticControl())->setValue('Server Default (' . $vary_default . ')');
     }
     $form->appendChild($re_control->setLabel('Add "Re:" Prefix')->setCaption('Enable this option to fix threading in Mail.app on OS X Lion, ' . 'or if you like "Re:" in your email subjects.'))->appendChild($vary_control->setLabel('Vary Subjects')->setCaption('This option adds more information to email subjects, but may ' . 'break threading in some clients.'));
     $form->appendChild('<br />' . '<p class="aphront-form-instructions">' . 'You can customize what mail you receive from Phabricator here.' . '</p>' . '<p class="aphront-form-instructions">' . '<strong>NOTE:</strong> If an update makes several changes (like ' . 'adding CCs to a task, closing it, and adding a comment) you will ' . 'still receive an email as long as at least one of the changes ' . 'is set to notify you.' . '</p>');
     $mailtags = $preferences->getPreference('mailtags', array());
     $form->appendChild($this->buildMailTagCheckboxes($this->getDifferentialMailTags(), $mailtags)->setLabel('Differential'))->appendChild($this->buildMailTagCheckboxes($this->getManiphestMailTags(), $mailtags)->setLabel('Maniphest'));
     $form->appendChild(id(new AphrontFormSubmitControl())->setValue('Save Preferences'));
     $panel = new AphrontPanelView();
     $panel->setHeader('Email Preferences');
     $panel->setWidth(AphrontPanelView::WIDTH_FORM);
     $panel->appendChild($form);
     return id(new AphrontNullView())->appendChild(array($notice, $panel));
 }
 public function processRequest(AphrontRequest $request)
 {
     $viewer = $this->getViewer();
     $user = $this->getUser();
     $preferences = $user->loadPreferences();
     $pref_re_prefix = PhabricatorUserPreferences::PREFERENCE_RE_PREFIX;
     $pref_vary = PhabricatorUserPreferences::PREFERENCE_VARY_SUBJECT;
     $prefs_html_email = PhabricatorUserPreferences::PREFERENCE_HTML_EMAILS;
     $errors = array();
     if ($request->isFormPost()) {
         if (PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
             if ($request->getStr($pref_re_prefix) == 'default') {
                 $preferences->unsetPreference($pref_re_prefix);
             } else {
                 $preferences->setPreference($pref_re_prefix, $request->getBool($pref_re_prefix));
             }
             if ($request->getStr($pref_vary) == 'default') {
                 $preferences->unsetPreference($pref_vary);
             } else {
                 $preferences->setPreference($pref_vary, $request->getBool($pref_vary));
             }
             if ($request->getStr($prefs_html_email) == 'default') {
                 $preferences->unsetPreference($prefs_html_email);
             } else {
                 $preferences->setPreference($prefs_html_email, $request->getBool($prefs_html_email));
             }
         }
         $preferences->save();
         return id(new AphrontRedirectResponse())->setURI($this->getPanelURI('?saved=true'));
     }
     $re_prefix_default = PhabricatorEnv::getEnvConfig('metamta.re-prefix') ? pht('Enabled') : pht('Disabled');
     $vary_default = PhabricatorEnv::getEnvConfig('metamta.vary-subjects') ? pht('Vary') : pht('Do Not Vary');
     $html_emails_default = pht('Plain Text');
     $re_prefix_value = $preferences->getPreference($pref_re_prefix);
     if ($re_prefix_value === null) {
         $re_prefix_value = 'default';
     } else {
         $re_prefix_value = $re_prefix_value ? 'true' : 'false';
     }
     $vary_value = $preferences->getPreference($pref_vary);
     if ($vary_value === null) {
         $vary_value = 'default';
     } else {
         $vary_value = $vary_value ? 'true' : 'false';
     }
     $html_emails_value = $preferences->getPreference($prefs_html_email);
     if ($html_emails_value === null) {
         $html_emails_value = 'default';
     } else {
         $html_emails_value = $html_emails_value ? 'true' : 'false';
     }
     $form = new AphrontFormView();
     $form->setUser($viewer);
     if (PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
         $html_email_control = id(new AphrontFormSelectControl())->setName($prefs_html_email)->setOptions(array('default' => pht('Default (%s)', $html_emails_default), 'true' => pht('Send HTML Email'), 'false' => pht('Send Plain Text Email')))->setValue($html_emails_value);
         $re_control = id(new AphrontFormSelectControl())->setName($pref_re_prefix)->setOptions(array('default' => pht('Use Server Default (%s)', $re_prefix_default), 'true' => pht('Enable "Re:" prefix'), 'false' => pht('Disable "Re:" prefix')))->setValue($re_prefix_value);
         $vary_control = id(new AphrontFormSelectControl())->setName($pref_vary)->setOptions(array('default' => pht('Use Server Default (%s)', $vary_default), 'true' => pht('Vary Subjects'), 'false' => pht('Do Not Vary Subjects')))->setValue($vary_value);
     } else {
         $html_email_control = id(new AphrontFormStaticControl())->setValue(pht('Server Default (%s)', $html_emails_default));
         $re_control = id(new AphrontFormStaticControl())->setValue(pht('Server Default (%s)', $re_prefix_default));
         $vary_control = id(new AphrontFormStaticControl())->setValue(pht('Server Default (%s)', $vary_default));
     }
     $form->appendRemarkupInstructions(pht('These settings fine-tune some technical aspects of how email is ' . 'formatted. You may be able to adjust them to make mail more ' . 'useful or improve threading.'));
     if (!PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
         $form->appendRemarkupInstructions(pht('NOTE: This install of Phabricator is configured to send a ' . 'single mail message to all recipients, so all settings are ' . 'locked at the server default value.'));
     }
     $form->appendRemarkupInstructions(pht("You can use the **HTML Email** setting to control whether " . "Phabricator send you HTML email (which has more color and " . "formatting) or plain text email (which is more compatible).\n" . "\n" . "WARNING: This feature is new and experimental! If you enable " . "it, mail may not render properly and replying to mail may not " . "work as well."))->appendChild($html_email_control->setLabel(pht('HTML Email')))->appendRemarkupInstructions('')->appendRemarkupInstructions(pht('The **Add "Re:" Prefix** setting adds "Re:" in front of all ' . 'messages, even if they are not replies. If you use **Mail.app** on ' . 'Mac OS X, this may improve mail threading.' . "\n\n" . "| Setting                | Example Mail Subject\n" . "|------------------------|----------------\n" . "| Enable \"Re:\" Prefix  | " . "`Re: [Differential] [Accepted] D123: Example Revision`\n" . "| Disable \"Re:\" Prefix | " . "`[Differential] [Accepted] D123: Example Revision`"))->appendChild($re_control->setLabel(pht('Add "Re:" Prefix')))->appendRemarkupInstructions('')->appendRemarkupInstructions(pht('With **Vary Subjects** enabled, most mail subject lines will ' . 'include a brief description of their content, like **[Closed]** ' . 'for a notification about someone closing a task.' . "\n\n" . "| Setting              | Example Mail Subject\n" . "|----------------------|----------------\n" . "| Vary Subjects        | " . "`[Maniphest] [Closed] T123: Example Task`\n" . "| Do Not Vary Subjects | " . "`[Maniphest] T123: Example Task`\n" . "\n" . 'This can make mail more useful, but some clients have difficulty ' . 'threading these messages. Disabling this option may improve ' . 'threading, at the cost of less useful subject lines.'))->appendChild($vary_control->setLabel(pht('Vary Subjects')));
     $form->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Save Preferences')));
     $form_box = id(new PHUIObjectBoxView())->setHeaderText(pht('Email Format'))->setFormSaved($request->getStr('saved'))->setFormErrors($errors)->setForm($form);
     return id(new AphrontNullView())->appendChild(array($form_box));
 }
<?php

$table = new PhabricatorMetaMTAMail();
$conn_w = $table->establishConnection('w');
echo pht('Assigning actorPHIDs to mails...') . "\n";
foreach (new LiskMigrationIterator($table) as $mail) {
    $id = $mail->getID();
    echo pht('Updating mail %d...', $id) . "\n";
    if ($mail->getActorPHID()) {
        continue;
    }
    $actor_phid = $mail->getFrom();
    if ($actor_phid === null) {
        continue;
    }
    queryfx($conn_w, 'UPDATE %T SET actorPHID = %s WHERE id = %d', $table->getTableName(), $actor_phid, $id);
}
echo pht('Done.') . "\n";
 private function addMailProjectMetadata(PhabricatorLiskDAO $object, PhabricatorMetaMTAMail $template)
 {
     $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs($object->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
     if (!$project_phids) {
         return;
     }
     // TODO: This viewer isn't quite right. It would be slightly better to use
     // the mail recipient, but that's not very easy given the way rendering
     // works today.
     $handles = id(new PhabricatorHandleQuery())->setViewer($this->requireActor())->withPHIDs($project_phids)->execute();
     $project_tags = array();
     foreach ($handles as $handle) {
         if (!$handle->isComplete()) {
             continue;
         }
         $project_tags[] = '<' . $handle->getObjectName() . '>';
     }
     if (!$project_tags) {
         return;
     }
     $project_tags = implode(', ', $project_tags);
     $template->addHeader('X-Phabricator-Projects', $project_tags);
 }
    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();
        }
    }