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)); }
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 isUserPanel() { return PhabricatorMetaMTAMail::shouldMultiplexAllMail(); }
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 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; }
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); }