2013-06-10 15:33:37 -04:00
|
|
|
#
|
|
|
|
# A helper class to send an email. It will also handle a nil message, which it considers
|
|
|
|
# to be "do nothing". This is because some Mailers will decide not to do work for some
|
|
|
|
# reason. For example, emailing a user too frequently. A nil to address is also considered
|
|
|
|
# "do nothing"
|
|
|
|
#
|
|
|
|
# It also adds an HTML part for the plain text body
|
|
|
|
#
|
|
|
|
require_dependency 'email/renderer'
|
2013-07-02 14:13:46 -04:00
|
|
|
require 'uri'
|
2014-03-12 06:55:08 -04:00
|
|
|
require 'net/smtp'
|
2013-06-10 15:33:37 -04:00
|
|
|
|
2014-03-07 10:33:15 -05:00
|
|
|
SMTP_CLIENT_ERRORS = [Net::SMTPFatalError, Net::SMTPSyntaxError]
|
|
|
|
|
2013-06-10 15:33:37 -04:00
|
|
|
module Email
|
|
|
|
class Sender
|
|
|
|
|
|
|
|
def initialize(message, email_type, user=nil)
|
|
|
|
@message = message
|
|
|
|
@email_type = email_type
|
|
|
|
@user = user
|
|
|
|
end
|
|
|
|
|
|
|
|
def send
|
2014-08-23 05:07:37 -04:00
|
|
|
return if SiteSetting.disable_emails
|
2016-02-17 11:31:46 -05:00
|
|
|
|
|
|
|
return if ActionMailer::Base::NullMail === @message
|
|
|
|
return if ActionMailer::Base::NullMail === (@message.message rescue nil)
|
|
|
|
|
2016-02-15 11:53:07 -05:00
|
|
|
return skip(I18n.t('email_log.message_blank')) if @message.blank?
|
2014-02-14 13:06:21 -05:00
|
|
|
return skip(I18n.t('email_log.message_to_blank')) if @message.to.blank?
|
2013-11-28 17:20:56 -05:00
|
|
|
|
|
|
|
if @message.text_part
|
2014-02-14 13:06:21 -05:00
|
|
|
return skip(I18n.t('email_log.text_part_body_blank')) if @message.text_part.body.to_s.blank?
|
2013-11-28 17:20:56 -05:00
|
|
|
else
|
2014-02-14 13:06:21 -05:00
|
|
|
return skip(I18n.t('email_log.body_blank')) if @message.body.to_s.blank?
|
2013-11-28 17:20:56 -05:00
|
|
|
end
|
2013-06-10 15:33:37 -04:00
|
|
|
|
|
|
|
@message.charset = 'UTF-8'
|
|
|
|
|
|
|
|
opts = {}
|
|
|
|
|
|
|
|
renderer = Email::Renderer.new(@message, opts)
|
|
|
|
|
2013-11-29 12:21:21 -05:00
|
|
|
if @message.html_part
|
|
|
|
@message.html_part.body = renderer.html
|
|
|
|
else
|
2013-07-24 03:13:15 -04:00
|
|
|
@message.html_part = Mail::Part.new do
|
|
|
|
content_type 'text/html; charset=UTF-8'
|
|
|
|
body renderer.html
|
|
|
|
end
|
2013-06-10 15:33:37 -04:00
|
|
|
end
|
|
|
|
|
2013-07-24 15:07:43 -04:00
|
|
|
@message.parts[0].body = @message.parts[0].body.to_s.gsub(/\[\/?email-indent\]/, '')
|
2013-07-22 15:06:37 -04:00
|
|
|
|
2015-01-17 05:07:58 -05:00
|
|
|
# Fix relative (ie upload) HTML links in markdown which do not work well in plain text emails.
|
|
|
|
# These are the links we add when a user uploads a file or image.
|
|
|
|
# Ideally we would parse general markdown into plain text, but that is almost an intractable problem.
|
|
|
|
url_prefix = Discourse.base_url
|
|
|
|
@message.parts[0].body = @message.parts[0].body.to_s.gsub(/<a class="attachment" href="(\/uploads\/default\/[^"]+)">([^<]*)<\/a>/, '[\2]('+url_prefix+'\1)')
|
|
|
|
@message.parts[0].body = @message.parts[0].body.to_s.gsub(/<img src="(\/uploads\/default\/[^"]+)"([^>]*)>/, '![]('+url_prefix+'\1)')
|
|
|
|
|
2013-06-10 15:33:37 -04:00
|
|
|
@message.text_part.content_type = 'text/plain; charset=UTF-8'
|
|
|
|
|
2013-06-25 11:35:26 -04:00
|
|
|
# Set up the email log
|
2016-01-29 10:49:49 -05:00
|
|
|
email_log = EmailLog.new(email_type: @email_type, to_address: to_address, user_id: @user.try(:id))
|
2013-07-02 14:13:46 -04:00
|
|
|
|
2013-07-08 11:48:40 -04:00
|
|
|
host = Email::Sender.host_for(Discourse.base_url)
|
2013-07-02 14:13:46 -04:00
|
|
|
|
2013-07-08 11:48:40 -04:00
|
|
|
topic_id = header_value('X-Discourse-Topic-Id')
|
|
|
|
post_id = header_value('X-Discourse-Post-Id')
|
|
|
|
reply_key = header_value('X-Discourse-Reply-Key')
|
|
|
|
|
2015-01-28 04:12:49 -05:00
|
|
|
# always set a default Message ID from the host
|
|
|
|
uuid = SecureRandom.uuid
|
|
|
|
@message.header['Message-ID'] = "<#{uuid}@#{host}>"
|
|
|
|
|
2013-07-08 11:48:40 -04:00
|
|
|
if topic_id.present?
|
|
|
|
email_log.topic_id = topic_id
|
|
|
|
|
2014-06-13 18:42:14 -04:00
|
|
|
topic_identifier = "<topic/#{topic_id}@#{host}>"
|
2014-11-26 19:35:56 -05:00
|
|
|
post_identifier = "<topic/#{topic_id}/#{post_id}@#{host}>"
|
|
|
|
@message.header['Message-ID'] = post_identifier
|
2014-06-13 18:42:14 -04:00
|
|
|
@message.header['In-Reply-To'] = topic_identifier
|
|
|
|
@message.header['References'] = topic_identifier
|
|
|
|
|
2014-10-08 14:09:21 -04:00
|
|
|
topic = Topic.where(id: topic_id).first
|
|
|
|
|
2014-06-13 18:42:14 -04:00
|
|
|
# http://www.ietf.org/rfc/rfc2919.txt
|
2014-10-08 14:09:21 -04:00
|
|
|
if topic && topic.category && !topic.category.uncategorized?
|
2015-10-21 04:13:20 -04:00
|
|
|
list_id = "<#{topic.category.name.downcase.gsub(' ', '-')}.#{host}>"
|
2014-10-08 14:09:21 -04:00
|
|
|
|
|
|
|
# subcategory case
|
|
|
|
if !topic.category.parent_category_id.nil?
|
|
|
|
parent_category_name = Category.find_by(id: topic.category.parent_category_id).name
|
2015-10-21 04:13:20 -04:00
|
|
|
list_id = "<#{topic.category.name.downcase.gsub(' ', '-')}.#{parent_category_name.downcase.gsub(' ', '-')}.#{host}>"
|
2014-10-08 14:09:21 -04:00
|
|
|
end
|
|
|
|
else
|
|
|
|
list_id = "<#{host}>"
|
|
|
|
end
|
2014-10-08 15:57:30 -04:00
|
|
|
|
|
|
|
# http://www.ietf.org/rfc/rfc3834.txt
|
2016-01-29 10:49:49 -05:00
|
|
|
@message.header['Precedence'] = 'list'
|
|
|
|
@message.header['List-ID'] = list_id
|
|
|
|
@message.header['List-Archive'] = topic.url if topic
|
2014-06-13 18:49:11 -04:00
|
|
|
end
|
|
|
|
|
2016-01-29 10:49:49 -05:00
|
|
|
if reply_key.present? && @message.header['Reply-To'] =~ /\<([^\>]+)\>/
|
|
|
|
email = Regexp.last_match[1]
|
|
|
|
@message.header['List-Post'] = "<mailto:#{email}>"
|
2013-07-08 11:48:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
email_log.post_id = post_id if post_id.present?
|
|
|
|
email_log.reply_key = reply_key if reply_key.present?
|
2013-06-13 10:56:16 -04:00
|
|
|
|
2013-06-25 11:35:26 -04:00
|
|
|
# Remove headers we don't need anymore
|
2016-01-29 10:49:49 -05:00
|
|
|
@message.header['X-Discourse-Topic-Id'] = nil if topic_id.present?
|
|
|
|
@message.header['X-Discourse-Post-Id'] = nil if post_id.present?
|
2015-01-29 06:53:10 -05:00
|
|
|
@message.header['X-Discourse-Reply-Key'] = nil if reply_key.present?
|
2013-06-25 11:35:26 -04:00
|
|
|
|
2014-09-13 01:26:31 -04:00
|
|
|
# Suppress images from short emails
|
2016-01-29 10:49:49 -05:00
|
|
|
if SiteSetting.strip_images_from_short_emails &&
|
|
|
|
@message.html_part.body.to_s.bytesize <= SiteSetting.short_email_length &&
|
|
|
|
@message.html_part.body =~ /<img[^>]+>/
|
2014-09-13 01:26:31 -04:00
|
|
|
style = Email::Styles.new(@message.html_part.body.to_s)
|
|
|
|
@message.html_part.body = style.strip_avatars_and_emojis
|
|
|
|
end
|
|
|
|
|
2014-03-07 10:33:15 -05:00
|
|
|
begin
|
2014-10-15 03:04:47 -04:00
|
|
|
@message.deliver_now
|
2014-03-09 08:06:54 -04:00
|
|
|
rescue *SMTP_CLIENT_ERRORS => e
|
2014-03-07 10:33:15 -05:00
|
|
|
return skip(e.message)
|
|
|
|
end
|
2013-06-25 11:35:26 -04:00
|
|
|
|
|
|
|
# Save and return the email log
|
2013-06-13 10:56:16 -04:00
|
|
|
email_log.save!
|
|
|
|
email_log
|
2013-06-10 15:33:37 -04:00
|
|
|
end
|
|
|
|
|
2014-02-14 13:06:21 -05:00
|
|
|
def to_address
|
|
|
|
@to_address ||= begin
|
2016-02-15 11:53:07 -05:00
|
|
|
to = @message.try(:to)
|
2016-01-29 10:49:49 -05:00
|
|
|
to = to.first if Array === to
|
|
|
|
to.presence || "no_email_found"
|
2014-02-14 13:06:21 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-07-08 11:48:40 -04:00
|
|
|
def self.host_for(base_url)
|
2013-07-02 14:13:46 -04:00
|
|
|
host = "localhost"
|
|
|
|
if base_url.present?
|
|
|
|
begin
|
|
|
|
uri = URI.parse(base_url)
|
|
|
|
host = uri.host.downcase if uri.host.present?
|
|
|
|
rescue URI::InvalidURIError
|
|
|
|
end
|
|
|
|
end
|
2013-07-08 11:48:40 -04:00
|
|
|
host
|
|
|
|
end
|
2013-07-02 14:13:46 -04:00
|
|
|
|
2013-06-13 18:11:10 -04:00
|
|
|
private
|
|
|
|
|
2013-07-08 11:48:40 -04:00
|
|
|
def header_value(name)
|
2013-06-13 18:11:10 -04:00
|
|
|
header = @message.header[name]
|
2013-07-08 11:48:40 -04:00
|
|
|
return nil unless header
|
|
|
|
header.value
|
2013-06-13 18:11:10 -04:00
|
|
|
end
|
|
|
|
|
2014-02-14 13:06:21 -05:00
|
|
|
def skip(reason)
|
2016-01-29 10:49:49 -05:00
|
|
|
EmailLog.create!(
|
|
|
|
email_type: @email_type,
|
|
|
|
to_address: to_address,
|
|
|
|
user_id: @user.try(:id),
|
|
|
|
skipped: true,
|
2016-02-15 11:53:07 -05:00
|
|
|
skipped_reason: "[Sender] #{reason}"
|
2016-01-29 10:49:49 -05:00
|
|
|
)
|
2014-02-14 13:06:21 -05:00
|
|
|
end
|
|
|
|
|
2013-06-10 15:33:37 -04:00
|
|
|
end
|
2013-07-24 03:13:15 -04:00
|
|
|
end
|