discourse/app/jobs/regular/user_email.rb
Sam Saffron 0c52537f10 DEV: update rubocop to version 0.77
We like to stay as close as possible to latest with rubocop cause the cops
get better.

This update required some code changes, specifically the default is to avoid
explicit returns where implicit is done

Also this renames a few rules
2019-12-10 11:48:39 +11:00

255 lines
8.5 KiB
Ruby

# frozen_string_literal: true
module Jobs
# Asynchronously send an email to a user
class UserEmail < ::Jobs::Base
include Skippable
sidekiq_options queue: 'low'
# Can be overridden by subclass, for example critical email
# should always consider being sent
def quit_email_early?
SiteSetting.disable_emails == 'yes'
end
def execute(args)
raise Discourse::InvalidParameters.new(:user_id) unless args[:user_id].present?
raise Discourse::InvalidParameters.new(:type) unless args[:type].present?
# This is for performance. Quit out fast without doing a bunch
# of extra work when emails are disabled.
return if quit_email_early?
post = nil
notification = nil
type = args[:type]
user = User.find_by(id: args[:user_id])
to_address = args[:to_address].presence || user.try(:email).presence || "no_email_found"
set_skip_context(type, args[:user_id], to_address, args[:post_id])
return skip(SkippedEmailLog.reason_types[:user_email_no_user]) unless user
if args[:post_id].present?
post = Post.find_by(id: args[:post_id])
unless post.present?
return skip(SkippedEmailLog.reason_types[:user_email_post_not_found])
end
if !Guardian.new(user).can_see?(post)
return skip(SkippedEmailLog.reason_types[:user_email_access_denied])
end
end
if args[:notification_id].present?
notification = Notification.find_by(id: args[:notification_id])
end
message, skip_reason_type = message_for_email(
user,
post,
type,
notification,
args
)
if message
Email::Sender.new(message, type, user).send
if (b = user.user_stat.bounce_score) > SiteSetting.bounce_score_erode_on_send
# erode bounce score each time we send an email
# this means that we are punished a lot less for bounces
# and we can recover more quickly
user.user_stat.update(bounce_score: b - SiteSetting.bounce_score_erode_on_send)
end
else
skip_reason_type
end
end
def set_skip_context(type, user_id, to_address, post_id)
@skip_context = { type: type, user_id: user_id, to_address: to_address, post_id: post_id }
end
NOTIFICATIONS_SENT_BY_MAILING_LIST ||= Set.new %w{
posted
replied
mentioned
group_mentioned
quoted
}
def message_for_email(user, post, type, notification, args = nil)
args ||= {}
notification_type = args[:notification_type]
notification_data_hash = args[:notification_data_hash]
email_token = args[:email_token]
to_address = args[:to_address]
set_skip_context(type, user.id, to_address || user.email, post.try(:id))
if user.anonymous?
return skip_message(SkippedEmailLog.reason_types[:user_email_anonymous_user])
end
if user.suspended? && !["user_private_message", "account_suspended"].include?(type.to_s)
return skip_message(SkippedEmailLog.reason_types[:user_email_user_suspended_not_pm])
end
if type.to_s == "digest"
return if user.staged
return if user.last_emailed_at &&
user.last_emailed_at >
(user.user_option&.digest_after_minutes || SiteSetting.default_email_digest_frequency.to_i).minutes.ago
end
seen_recently = (user.last_seen_at.present? && user.last_seen_at > SiteSetting.email_time_window_mins.minutes.ago)
seen_recently = false if always_email_regular?(user, type) || always_email_private_message?(user, type) || user.staged
email_args = {}
if (post || notification || notification_type) &&
(seen_recently && !user.suspended?)
return skip_message(SkippedEmailLog.reason_types[:user_email_seen_recently])
end
email_args[:post] = post if post
if notification || notification_type
email_args[:notification_type] ||= notification_type || notification.try(:notification_type)
email_args[:notification_data_hash] ||= notification_data_hash || notification.try(:data_hash)
unless String === email_args[:notification_type]
if Numeric === email_args[:notification_type]
email_args[:notification_type] = Notification.types[email_args[:notification_type]]
end
email_args[:notification_type] = email_args[:notification_type].to_s
end
if user.user_option.mailing_list_mode? &&
user.user_option.mailing_list_mode_frequency > 0 && # don't catch notifications for users on daily mailing list mode
(!post.try(:topic).try(:private_message?)) &&
NOTIFICATIONS_SENT_BY_MAILING_LIST.include?(email_args[:notification_type])
# no need to log a reason when the mail was already sent via the mailing list job
return [nil, nil]
end
unless always_email_regular?(user, type) || always_email_private_message?(user, type)
if (notification && notification.read?) || (post && post.seen?(user))
return skip_message(SkippedEmailLog.reason_types[:user_email_notification_already_read])
end
end
end
skip_reason_type = skip_email_for_post(post, user)
return skip_message(skip_reason_type) if skip_reason_type.present?
# Make sure that mailer exists
raise Discourse::InvalidParameters.new("type=#{type}") unless UserNotifications.respond_to?(type)
email_args[:email_token] = email_token if email_token.present?
email_args[:new_email] = user.email if type.to_s == "notify_old_email"
if args[:client_ip] && args[:user_agent]
email_args[:client_ip] = args[:client_ip]
email_args[:user_agent] = args[:user_agent]
end
if EmailLog.reached_max_emails?(user, type.to_s)
return skip_message(SkippedEmailLog.reason_types[:exceeded_emails_limit])
end
if !EmailLog::CRITICAL_EMAIL_TYPES.include?(type.to_s) && user.user_stat.bounce_score >= SiteSetting.bounce_score_threshold
return skip_message(SkippedEmailLog.reason_types[:exceeded_bounces_limit])
end
if args[:user_history_id]
email_args[:user_history] = UserHistory.where(id: args[:user_history_id]).first
end
message = EmailLog.unique_email_per_post(post, user) do
UserNotifications.public_send(type, user, email_args)
end
# Update the to address if we have a custom one
message.to = to_address if message && to_address.present?
[message, nil]
end
sidekiq_retry_in do |count, exception|
# retry in an hour when SMTP server is busy
# or use default sidekiq retry formula
case exception.wrapped
when Net::SMTPServerBusy
1.hour + (rand(30) * (count + 1))
else
::Jobs::UserEmail.seconds_to_delay(count)
end
end
# extracted from sidekiq
def self.seconds_to_delay(count)
(count**4) + 15 + (rand(30) * (count + 1))
end
private
def skip_message(reason)
[nil, skip(reason)]
end
# If this email has a related post, don't send an email if it's been deleted or seen recently.
def skip_email_for_post(post, user)
if post
if post.topic.blank?
return SkippedEmailLog.reason_types[:user_email_topic_nil]
end
if post.user.blank?
return SkippedEmailLog.reason_types[:user_email_post_user_deleted]
end
if post.user_deleted?
return SkippedEmailLog.reason_types[:user_email_post_deleted]
end
if user.suspended? && !post.user&.staff?
return SkippedEmailLog.reason_types[:user_email_user_suspended]
end
already_read = user.user_option.email_level != UserOption.email_level_types[:always] && PostTiming.exists?(topic_id: post.topic_id, post_number: post.post_number, user_id: user.id)
if already_read
SkippedEmailLog.reason_types[:user_email_already_read]
end
else
false
end
end
def skip(reason_type)
create_skipped_email_log(
email_type: @skip_context[:type],
to_address: @skip_context[:to_address],
user_id: @skip_context[:user_id],
post_id: @skip_context[:post_id],
reason_type: reason_type
)
end
def always_email_private_message?(user, type)
type == :user_private_message && user.user_option.email_messages_level == UserOption.email_level_types[:always]
end
def always_email_regular?(user, type)
type != :user_private_message && user.user_option.email_level == UserOption.email_level_types[:always]
end
end
end