mirror of
https://github.com/discourse/discourse.git
synced 2024-12-12 04:04:17 +08:00
a09dc2d5c2
When sending emails out via group SMTP, if we are sending them to non-staged users we want to mask those emails with BCC, just so we don't expose them to anyone we shouldn't. Staged users are ones that have likely only interacted with support via email, and will likely include other people who were CC'd on the original email to the group. Co-authored-by: Martin Brennan <martin@discourse.org>
256 lines
9.1 KiB
Ruby
256 lines
9.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# Builds a Mail::Message we can use for sending. Optionally supports using a template
|
|
# for the body and subject
|
|
module Email
|
|
class MessageBuilder
|
|
attr_reader :template_args
|
|
|
|
ALLOW_REPLY_BY_EMAIL_HEADER = 'X-Discourse-Allow-Reply-By-Email'
|
|
|
|
def initialize(to, opts = nil)
|
|
@to = to
|
|
@opts = opts || {}
|
|
|
|
@template_args = {
|
|
site_name: SiteSetting.title,
|
|
email_prefix: SiteSetting.email_prefix.presence || SiteSetting.title,
|
|
base_url: Discourse.base_url,
|
|
user_preferences_url: "#{Discourse.base_url}/my/preferences",
|
|
hostname: Discourse.current_hostname,
|
|
}.merge!(@opts)
|
|
|
|
if @template_args[:url].present?
|
|
@template_args[:header_instructions] ||= I18n.t('user_notifications.header_instructions', @template_args)
|
|
|
|
if @opts[:include_respond_instructions] == false
|
|
@template_args[:respond_instructions] = ''
|
|
@template_args[:respond_instructions] = I18n.t('user_notifications.pm_participants', @template_args) if @opts[:private_reply]
|
|
else
|
|
if @opts[:only_reply_by_email]
|
|
string = +"user_notifications.only_reply_by_email"
|
|
string << "_pm" if @opts[:private_reply]
|
|
else
|
|
string = allow_reply_by_email? ? +"user_notifications.reply_by_email" : +"user_notifications.visit_link_to_respond"
|
|
string << "_pm" if @opts[:private_reply]
|
|
end
|
|
@template_args[:respond_instructions] = "---\n" + I18n.t(string, @template_args)
|
|
end
|
|
|
|
if @opts[:add_unsubscribe_link]
|
|
unsubscribe_string = if @opts[:mailing_list_mode]
|
|
"unsubscribe_mailing_list"
|
|
elsif SiteSetting.unsubscribe_via_email_footer
|
|
"unsubscribe_link_and_mail"
|
|
else
|
|
"unsubscribe_link"
|
|
end
|
|
@template_args[:unsubscribe_instructions] = I18n.t(unsubscribe_string, @template_args)
|
|
end
|
|
end
|
|
end
|
|
|
|
def subject
|
|
if @opts[:template] &&
|
|
TranslationOverride.exists?(locale: I18n.locale, translation_key: "#{@opts[:template]}.subject_template")
|
|
augmented_template_args = @template_args.merge({
|
|
site_name: @template_args[:email_prefix],
|
|
optional_re: @opts[:add_re_to_subject] ? I18n.t('subject_re') : '',
|
|
optional_pm: @opts[:private_reply] ? @template_args[:subject_pm] : '',
|
|
optional_cat: @template_args[:show_category_in_subject] ? "[#{@template_args[:show_category_in_subject]}] " : '',
|
|
optional_tags: @template_args[:show_tags_in_subject] ? "#{@template_args[:show_tags_in_subject]} " : '',
|
|
topic_title: @template_args[:topic_title] ? @template_args[:topic_title] : '',
|
|
})
|
|
subject = I18n.t("#{@opts[:template]}.subject_template", augmented_template_args)
|
|
elsif @opts[:use_site_subject]
|
|
subject = String.new(SiteSetting.email_subject)
|
|
subject.gsub!("%{site_name}", @template_args[:email_prefix])
|
|
subject.gsub!("%{optional_re}", @opts[:add_re_to_subject] ? I18n.t('subject_re') : '')
|
|
subject.gsub!("%{optional_pm}", @opts[:private_reply] ? @template_args[:subject_pm] : '')
|
|
subject.gsub!("%{optional_cat}", @template_args[:show_category_in_subject] ? "[#{@template_args[:show_category_in_subject]}] " : '')
|
|
subject.gsub!("%{optional_tags}", @template_args[:show_tags_in_subject] ? "#{@template_args[:show_tags_in_subject]} " : '')
|
|
subject.gsub!("%{topic_title}", @template_args[:topic_title]) if @template_args[:topic_title] # must be last for safety
|
|
elsif @opts[:use_topic_title_subject]
|
|
subject = @opts[:add_re_to_subject] ? I18n.t('subject_re') : ''
|
|
subject = "#{subject}#{@template_args[:topic_title]}"
|
|
elsif @opts[:template]
|
|
subject = I18n.t("#{@opts[:template]}.subject_template", @template_args)
|
|
else
|
|
subject = @opts[:subject]
|
|
end
|
|
subject
|
|
end
|
|
|
|
def html_part
|
|
return unless html_override = @opts[:html_override]
|
|
|
|
if @template_args[:unsubscribe_instructions].present?
|
|
unsubscribe_instructions = PrettyText.cook(@template_args[:unsubscribe_instructions], sanitize: false).html_safe
|
|
html_override.gsub!("%{unsubscribe_instructions}", unsubscribe_instructions)
|
|
else
|
|
html_override.gsub!("%{unsubscribe_instructions}", "")
|
|
end
|
|
|
|
if @template_args[:header_instructions].present?
|
|
header_instructions = PrettyText.cook(@template_args[:header_instructions], sanitize: false).html_safe
|
|
html_override.gsub!("%{header_instructions}", header_instructions)
|
|
else
|
|
html_override.gsub!("%{header_instructions}", "")
|
|
end
|
|
|
|
if @template_args[:respond_instructions].present?
|
|
respond_instructions = PrettyText.cook(@template_args[:respond_instructions], sanitize: false).html_safe
|
|
html_override.gsub!("%{respond_instructions}", respond_instructions)
|
|
else
|
|
html_override.gsub!("%{respond_instructions}", "")
|
|
end
|
|
|
|
html = UserNotificationRenderer.render(
|
|
template: 'layouts/email_template',
|
|
format: :html,
|
|
locals: { html_body: html_override.html_safe }
|
|
)
|
|
|
|
Mail::Part.new do
|
|
content_type 'text/html; charset=UTF-8'
|
|
body html
|
|
end
|
|
end
|
|
|
|
def body
|
|
body = nil
|
|
|
|
if @opts[:template]
|
|
body = I18n.t("#{@opts[:template]}.text_body_template", template_args).dup
|
|
else
|
|
body = @opts[:body].dup
|
|
end
|
|
|
|
if @template_args[:unsubscribe_instructions].present?
|
|
body << "\n"
|
|
body << @template_args[:unsubscribe_instructions]
|
|
end
|
|
|
|
body
|
|
end
|
|
|
|
def build_args
|
|
args = {
|
|
to: @to,
|
|
subject: subject,
|
|
body: body,
|
|
charset: 'UTF-8',
|
|
from: from_value,
|
|
cc: @opts[:cc],
|
|
bcc: @opts[:bcc]
|
|
}
|
|
|
|
args[:delivery_method_options] = @opts[:delivery_method_options] if @opts[:delivery_method_options]
|
|
|
|
args
|
|
end
|
|
|
|
def header_args
|
|
result = {}
|
|
if @opts[:add_unsubscribe_link]
|
|
unsubscribe_url = @template_args[:unsubscribe_url].presence || @template_args[:user_preferences_url]
|
|
result['List-Unsubscribe'] = "<#{unsubscribe_url}>"
|
|
end
|
|
|
|
result['X-Discourse-Post-Id'] = @opts[:post_id].to_s if @opts[:post_id]
|
|
result['X-Discourse-Topic-Id'] = @opts[:topic_id].to_s if @opts[:topic_id]
|
|
|
|
# please, don't send us automatic responses...
|
|
result['X-Auto-Response-Suppress'] = 'All'
|
|
|
|
if !allow_reply_by_email?
|
|
# This will end up being the notification_email, which is a
|
|
# noreply address.
|
|
result['Reply-To'] = from_value
|
|
else
|
|
|
|
# The only reason we use from address for reply to is for group
|
|
# SMTP emails, where the person will be replying to the group's
|
|
# email_username.
|
|
if !@opts[:use_from_address_for_reply_to]
|
|
result[ALLOW_REPLY_BY_EMAIL_HEADER] = true
|
|
result['Reply-To'] = reply_by_email_address
|
|
else
|
|
# No point in adding a reply-to header if it is going to be identical
|
|
# to the from address/alias. If the from option is not present, then
|
|
# the default reply-to address is used.
|
|
result['Reply-To'] = from_value if from_value != alias_email(@opts[:from])
|
|
end
|
|
end
|
|
|
|
result.merge(MessageBuilder.custom_headers(SiteSetting.email_custom_headers))
|
|
end
|
|
|
|
def self.custom_headers(string)
|
|
result = {}
|
|
string.split('|').each { |item|
|
|
header = item.split(':', 2)
|
|
if header.length == 2
|
|
name = header[0].strip
|
|
value = header[1].strip
|
|
result[name] = value if name.length > 0 && value.length > 0
|
|
end
|
|
} unless string.nil?
|
|
result
|
|
end
|
|
|
|
protected
|
|
|
|
def allow_reply_by_email?
|
|
SiteSetting.reply_by_email_enabled? &&
|
|
reply_by_email_address.present? &&
|
|
@opts[:allow_reply_by_email]
|
|
end
|
|
|
|
def private_reply?
|
|
allow_reply_by_email? && @opts[:private_reply]
|
|
end
|
|
|
|
def from_value
|
|
return @from_value if @from_value
|
|
@from_value = @opts[:from] || SiteSetting.notification_email
|
|
@from_value = alias_email(@from_value)
|
|
end
|
|
|
|
def reply_by_email_address
|
|
return @reply_by_email_address if @reply_by_email_address
|
|
return nil unless SiteSetting.reply_by_email_address.present?
|
|
|
|
@reply_by_email_address = SiteSetting.reply_by_email_address.dup
|
|
|
|
@reply_by_email_address =
|
|
if private_reply?
|
|
alias_email(@reply_by_email_address)
|
|
else
|
|
site_alias_email(@reply_by_email_address)
|
|
end
|
|
end
|
|
|
|
def alias_email(source)
|
|
return source if @opts[:from_alias].blank? &&
|
|
SiteSetting.email_site_title.blank? &&
|
|
SiteSetting.title.blank?
|
|
|
|
if @opts[:from_alias].present?
|
|
%Q|"#{Email.cleanup_alias(@opts[:from_alias])}" <#{source}>|
|
|
elsif source == SiteSetting.notification_email || source == SiteSetting.reply_by_email_address
|
|
site_alias_email(source)
|
|
else
|
|
source
|
|
end
|
|
end
|
|
|
|
def site_alias_email(source)
|
|
from_alias = Email.site_title
|
|
%Q|"#{Email.cleanup_alias(from_alias)}" <#{source}>|
|
|
end
|
|
|
|
end
|
|
|
|
end
|