mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-18 11:31:20 +08:00
Adds a preview text to the beginning of the email body based on user notification type. The value is taken from the translation key, which can be customized for each email within Admin -> Appearance -> Site Texts (ie. translation overrides). For html emails this preview text is hidden with `display: none` to prevent it appearing within the body of the email. For the plain text version of the email it will appear within the body of the email. Co-authored-by: Martin Brennan <martin@discourse.org>
398 lines
13 KiB
Ruby
398 lines
13 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, :reply_by_email_key
|
|
|
|
ALLOW_REPLY_BY_EMAIL_HEADER = "X-Discourse-Allow-Reply-By-Email"
|
|
INSTRUCTIONS_SEPARATOR = "---\n"
|
|
|
|
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 @opts[:template].present? && I18n.exists?("#{@opts[:template]}.preview")
|
|
@template_args[:email_preview] ||= I18n.t("#{@opts[:template]}.preview", @template_args)
|
|
end
|
|
|
|
return if @template_args[:url].blank?
|
|
|
|
@template_args[:header_instructions] ||= I18n.t(
|
|
"user_notifications.header_instructions",
|
|
@template_args,
|
|
)
|
|
@visit_link_to_respond_key =
|
|
DiscoursePluginRegistry.apply_modifier(
|
|
:message_builder_visit_link_to_respond,
|
|
"user_notifications.visit_link_to_respond",
|
|
@opts,
|
|
@to,
|
|
)
|
|
@reply_by_email_key =
|
|
DiscoursePluginRegistry.apply_modifier(
|
|
:message_builder_reply_by_email,
|
|
"user_notifications.reply_by_email",
|
|
@opts,
|
|
@to,
|
|
)
|
|
|
|
if @opts[:include_respond_instructions] == false
|
|
if @opts[:private_reply]
|
|
@template_args[:respond_instructions] = I18n.t(
|
|
"user_notifications.pm_participants",
|
|
@template_args,
|
|
)
|
|
else
|
|
@template_args[:respond_instructions] = ""
|
|
end
|
|
else
|
|
if @opts[:only_reply_by_email]
|
|
respond_instructions_key = +"user_notifications.only_reply_by_email"
|
|
if @opts[:private_reply]
|
|
if @opts[:username] == Discourse.system_user.username
|
|
respond_instructions_key << "_pm_button_only"
|
|
else
|
|
respond_instructions_key << "_pm"
|
|
end
|
|
end
|
|
else
|
|
respond_instructions_key =
|
|
(
|
|
if allow_reply_by_email?
|
|
+@reply_by_email_key
|
|
else
|
|
+@visit_link_to_respond_key
|
|
end
|
|
)
|
|
if @opts[:private_reply]
|
|
if @opts[:username] == Discourse.system_user.username
|
|
respond_instructions_key << "_pm_button_only"
|
|
else
|
|
respond_instructions_key << "_pm"
|
|
end
|
|
end
|
|
end
|
|
@template_args[:respond_instructions] = (
|
|
if respond_instructions_key != ""
|
|
INSTRUCTIONS_SEPARATOR + I18n.t(respond_instructions_key, @template_args)
|
|
else
|
|
""
|
|
end
|
|
)
|
|
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
|
|
|
|
def subject
|
|
has_override =
|
|
TranslationOverride.exists?(
|
|
locale: I18n.locale,
|
|
translation_key: "#{@opts[:template]}.subject_template",
|
|
)
|
|
|
|
if @opts[:template] && has_override
|
|
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: format_category,
|
|
optional_tags: format_tags,
|
|
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}", format_category)
|
|
subject.gsub!("%{optional_tags}", format_tags)
|
|
if @template_args[:topic_title]
|
|
subject.gsub!("%{topic_title}", @template_args[:topic_title])
|
|
end
|
|
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
|
|
|
|
DiscoursePluginRegistry.apply_modifier(:message_builder_subject, subject, @opts, @to)
|
|
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[:email_preview].present?
|
|
email_preview = PrettyText.cook(@template_args[:email_preview], sanitize: false).html_safe
|
|
html_override.gsub!("%{email_preview}", email_preview)
|
|
else
|
|
html_override.gsub!("%{email_preview}", "")
|
|
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,
|
|
},
|
|
)
|
|
html = DiscoursePluginRegistry.apply_modifier(:message_builder_html_part, html, @opts, @to)
|
|
|
|
Mail::Part.new do
|
|
content_type "text/html; charset=UTF-8"
|
|
body html
|
|
end
|
|
end
|
|
|
|
def body
|
|
body = nil
|
|
|
|
if @opts[:template]
|
|
%i[topic_title inviter_name].each do |key|
|
|
@template_args[key] = escaped_template_arg(key) if @template_args.key?(key)
|
|
end
|
|
|
|
augmented_template_args =
|
|
@template_args.merge(
|
|
optional_re: "",
|
|
optional_pm: "",
|
|
optional_cat: format_category,
|
|
optional_tags: format_tags,
|
|
)
|
|
|
|
body = I18n.t("#{@opts[:template]}.text_body_template", augmented_template_args).dup
|
|
else
|
|
body = @opts[:body].dup
|
|
end
|
|
|
|
if @template_args[:unsubscribe_instructions].present?
|
|
body << "\n"
|
|
body << @template_args[:unsubscribe_instructions]
|
|
end
|
|
DiscoursePluginRegistry.apply_modifier(:message_builder_body, body, @opts, @to)
|
|
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[:delivery_method_options] = (args[:delivery_method_options] || {}).merge(
|
|
return_response: true,
|
|
)
|
|
|
|
args
|
|
end
|
|
|
|
def header_args
|
|
result = {}
|
|
|
|
if @template_args[:email_preview].present?
|
|
result["X-Discourse-Email-Preview"] = @template_args[:email_preview]
|
|
end
|
|
|
|
if @opts[:add_unsubscribe_link]
|
|
if unsubscribe_url = @template_args[:unsubscribe_url].presence
|
|
result["List-Unsubscribe"] = "<#{unsubscribe_url}>"
|
|
result["List-Unsubscribe-Post"] = "List-Unsubscribe=One-Click"
|
|
else
|
|
result["List-Unsubscribe"] = "<#{@template_args[:user_preferences_url]}>"
|
|
end
|
|
end
|
|
|
|
result["X-Discourse-Post-Id"] = @opts[:post_id].to_s if @opts[:post_id]
|
|
result["X-Discourse-Post-Ids"] = @opts[:post_ids].join(",") if @opts[:post_ids].present?
|
|
result["X-Discourse-Topic-Id"] = @opts[:topic_id].to_s if @opts[:topic_id]
|
|
result["X-Discourse-Topic-Ids"] = @opts[:topic_ids].join(",") if @opts[:topic_ids].present?
|
|
|
|
# At this point these have been filtered by the recipient's guardian for visibility,
|
|
# see UserNotifications#send_notification_email
|
|
result["X-Discourse-Tags"] = @template_args[:show_tags_in_subject] if @opts[
|
|
:show_tags_in_subject
|
|
]
|
|
result["X-Discourse-Category"] = @template_args[:show_category_in_subject] if @opts[
|
|
:show_category_in_subject
|
|
]
|
|
|
|
# Mimics X-GitHub-Sender, which identifies the GitHub user that originated the message,
|
|
# useful to filter and prioritize mail.
|
|
result["X-Discourse-Sender"] = @opts[:username] if @opts[:username].present?
|
|
|
|
# Please, don't send us automatic responses...
|
|
result["X-Auto-Response-Suppress"] = "All"
|
|
|
|
# Disable Outlook's noisy "reaction via email" feature
|
|
result["x-ms-reactions"] = "disallow"
|
|
|
|
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 do |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
|
|
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 if SiteSetting.reply_by_email_address.blank?
|
|
|
|
@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)
|
|
if @opts[:from_alias].blank? && SiteSetting.email_site_title.blank? &&
|
|
SiteSetting.title.blank?
|
|
return source
|
|
end
|
|
|
|
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
|
|
|
|
private
|
|
|
|
def escaped_template_arg(key)
|
|
value = template_args[key].dup
|
|
# explicitly escaped twice, as Mailers will mark the body as html_safe
|
|
once_escaped = String.new(ERB::Util.html_escape(value))
|
|
ERB::Util.html_escape(once_escaped)
|
|
end
|
|
|
|
def format_category
|
|
if @template_args[:show_category_in_subject]
|
|
"[#{@template_args[:show_category_in_subject]}] "
|
|
else
|
|
""
|
|
end
|
|
end
|
|
|
|
def format_tags
|
|
if @template_args[:show_tags_in_subject]
|
|
"#{@template_args[:show_tags_in_subject]} "
|
|
else
|
|
""
|
|
end
|
|
end
|
|
end
|
|
end
|