498 lines
18 KiB
Ruby
498 lines
18 KiB
Ruby
require_dependency 'markdown_linker'
|
|
require_dependency 'email/message_builder'
|
|
require_dependency 'age_words'
|
|
|
|
class UserNotifications < ActionMailer::Base
|
|
include UserNotificationsHelper
|
|
include ApplicationHelper
|
|
helper :application
|
|
default charset: 'UTF-8'
|
|
|
|
include Email::BuildEmailHelper
|
|
|
|
def signup(user, opts={})
|
|
build_email(user.email,
|
|
template: "user_notifications.signup",
|
|
locale: user_locale(user),
|
|
email_token: opts[:email_token])
|
|
end
|
|
|
|
def signup_after_approval(user, opts={})
|
|
build_email(user.email,
|
|
template: 'user_notifications.signup_after_approval',
|
|
locale: user_locale(user),
|
|
email_token: opts[:email_token],
|
|
new_user_tips: I18n.t('system_messages.usage_tips.text_body_template', base_url: Discourse.base_url, locale: locale))
|
|
end
|
|
|
|
def notify_old_email(user, opts={})
|
|
build_email(user.email,
|
|
template: "user_notifications.notify_old_email",
|
|
locale: user_locale(user),
|
|
new_email: opts[:new_email])
|
|
end
|
|
|
|
def confirm_old_email(user, opts={})
|
|
build_email(user.email,
|
|
template: "user_notifications.confirm_old_email",
|
|
locale: user_locale(user),
|
|
email_token: opts[:email_token])
|
|
end
|
|
|
|
def confirm_new_email(user, opts={})
|
|
build_email(user.email,
|
|
template: "user_notifications.confirm_new_email",
|
|
locale: user_locale(user),
|
|
email_token: opts[:email_token])
|
|
end
|
|
|
|
def forgot_password(user, opts={})
|
|
build_email(user.email,
|
|
template: user.has_password? ? "user_notifications.forgot_password" : "user_notifications.set_password",
|
|
locale: user_locale(user),
|
|
email_token: opts[:email_token])
|
|
end
|
|
|
|
def admin_login(user, opts={})
|
|
build_email(user.email,
|
|
template: "user_notifications.admin_login",
|
|
locale: user_locale(user),
|
|
email_token: opts[:email_token])
|
|
end
|
|
|
|
def account_created(user, opts={})
|
|
build_email(user.email,
|
|
template: "user_notifications.account_created",
|
|
locale: user_locale(user),
|
|
email_token: opts[:email_token])
|
|
end
|
|
|
|
def short_date(dt)
|
|
if dt.year == Time.now.year
|
|
I18n.l(dt, format: :short_no_year)
|
|
else
|
|
I18n.l(dt, format: :date_only)
|
|
end
|
|
end
|
|
|
|
# This is the "mailing list summary email"
|
|
def mailing_list(user, opts={})
|
|
@since = opts[:since] || 1.day.ago
|
|
@since_formatted = short_date(@since)
|
|
|
|
@new_topic_posts = Post.mailing_list_new_topics(user, @since).group_by(&:topic) || {}
|
|
@existing_topic_posts = Post.mailing_list_updates(user, @since).group_by(&:topic) || {}
|
|
@posts_by_topic = @new_topic_posts.merge @existing_topic_posts
|
|
return unless @posts_by_topic.present?
|
|
|
|
build_summary_for(user)
|
|
opts = {
|
|
from_alias: I18n.t('user_notifications.mailing_list.from', site_name: SiteSetting.title),
|
|
subject: I18n.t('user_notifications.mailing_list.subject_template', email_prefix: @email_prefix, date: @date),
|
|
mailing_list_mode: true,
|
|
add_unsubscribe_link: true,
|
|
unsubscribe_url: "#{Discourse.base_url}/email/unsubscribe/#{@unsubscribe_key}",
|
|
}
|
|
apply_notification_styles(build_email(@user.email, opts))
|
|
end
|
|
|
|
def digest(user, opts={})
|
|
build_summary_for(user)
|
|
min_date = opts[:since] || user.last_emailed_at || user.last_seen_at || 1.month.ago
|
|
|
|
# Fetch some topics and posts to show
|
|
digest_opts = {limit: SiteSetting.digest_topics + SiteSetting.digest_other_topics, top_order: true}
|
|
topics_for_digest = Topic.for_digest(user, min_date, digest_opts).to_a
|
|
if topics_for_digest.empty? && !user.user_option.try(:include_tl0_in_digests)
|
|
# Find some topics from new users that are at least 24 hours old
|
|
topics_for_digest = Topic.for_digest(user, min_date, digest_opts.merge(include_tl0: true)).where('topics.created_at < ?', 24.hours.ago).to_a
|
|
end
|
|
|
|
@popular_topics = topics_for_digest[0,SiteSetting.digest_topics]
|
|
|
|
if @popular_topics.present?
|
|
@other_new_for_you = topics_for_digest.size > SiteSetting.digest_topics ? topics_for_digest[SiteSetting.digest_topics..-1] : []
|
|
|
|
@popular_posts = if SiteSetting.digest_posts > 0
|
|
Post.order("posts.score DESC")
|
|
.for_mailing_list(user, min_date)
|
|
.where('posts.post_type = ?', Post.types[:regular])
|
|
.where('posts.deleted_at IS NULL AND posts.hidden = false AND posts.user_deleted = false')
|
|
.where("posts.post_number > ? AND posts.score > ?", 1, ScoreCalculator.default_score_weights[:like_score] * 1.0)
|
|
.limit(SiteSetting.digest_posts)
|
|
else
|
|
[]
|
|
end
|
|
|
|
@excerpts = {}
|
|
|
|
@popular_topics.map do |t|
|
|
@excerpts[t.first_post.id] = email_excerpt(t.first_post.cooked) if t.first_post.present?
|
|
end
|
|
|
|
# Try to find 3 interesting stats for the top of the digest
|
|
|
|
new_topics_count = Topic.new_since_last_seen(user, min_date).count
|
|
if new_topics_count == 0
|
|
# We used topics from new users instead, so count should match
|
|
new_topics_count = topics_for_digest.size
|
|
end
|
|
@counts = [{label_key: 'user_notifications.digest.new_topics',
|
|
value: new_topics_count,
|
|
href: "#{Discourse.base_url}/new"}]
|
|
|
|
value = user.unread_notifications
|
|
@counts << {label_key: 'user_notifications.digest.unread_notifications', value: value, href: "#{Discourse.base_url}/my/notifications"} if value > 0
|
|
|
|
value = user.unread_private_messages
|
|
@counts << {label_key: 'user_notifications.digest.unread_messages', value: value, href: "#{Discourse.base_url}/my/messages"} if value > 0
|
|
|
|
if @counts.size < 3
|
|
value = user.unread_notifications_of_type(Notification.types[:liked])
|
|
@counts << {label_key: 'user_notifications.digest.liked_received', value: value, href: "#{Discourse.base_url}/my/notifications"} if value > 0
|
|
end
|
|
|
|
if @counts.size < 3
|
|
value = Post.for_mailing_list(user, min_date).where("posts.post_number > ?", 1).count
|
|
@counts << { label_key: 'user_notifications.digest.new_posts', value: value, href: "#{Discourse.base_url}/new" } if value > 0
|
|
end
|
|
|
|
if @counts.size < 3
|
|
value = User.real.where(active: true, staged: false).not_suspended.where("created_at > ?", min_date).count
|
|
@counts << { label_key: 'user_notifications.digest.new_users', value: value, href: "#{Discourse.base_url}/about" } if value > 0
|
|
end
|
|
|
|
@last_seen_at = short_date(user.last_seen_at || user.created_at)
|
|
|
|
@preheader_text = I18n.t('user_notifications.digest.preheader', last_seen_at: @last_seen_at)
|
|
|
|
opts = {
|
|
from_alias: I18n.t('user_notifications.digest.from', site_name: SiteSetting.title),
|
|
subject: I18n.t('user_notifications.digest.subject_template', email_prefix: @email_prefix, date: short_date(Time.now)),
|
|
add_unsubscribe_link: true,
|
|
unsubscribe_url: "#{Discourse.base_url}/email/unsubscribe/#{@unsubscribe_key}",
|
|
}
|
|
|
|
build_email(user.email, opts)
|
|
end
|
|
end
|
|
|
|
|
|
def user_replied(user, opts)
|
|
opts[:allow_reply_by_email] = true
|
|
opts[:use_site_subject] = true
|
|
opts[:show_category_in_subject] = true
|
|
notification_email(user, opts)
|
|
end
|
|
|
|
def user_quoted(user, opts)
|
|
opts[:allow_reply_by_email] = true
|
|
opts[:use_site_subject] = true
|
|
opts[:show_category_in_subject] = true
|
|
notification_email(user, opts)
|
|
end
|
|
|
|
def user_linked(user, opts)
|
|
opts[:allow_reply_by_email] = true
|
|
opts[:use_site_subject] = true
|
|
opts[:show_category_in_subject] = true
|
|
notification_email(user, opts)
|
|
end
|
|
|
|
def user_mentioned(user, opts)
|
|
opts[:allow_reply_by_email] = true
|
|
opts[:use_site_subject] = true
|
|
opts[:show_category_in_subject] = true
|
|
notification_email(user, opts)
|
|
end
|
|
|
|
def group_mentioned(user, opts)
|
|
opts[:allow_reply_by_email] = true
|
|
opts[:use_site_subject] = true
|
|
opts[:show_category_in_subject] = true
|
|
notification_email(user, opts)
|
|
end
|
|
|
|
def user_posted(user, opts)
|
|
opts[:allow_reply_by_email] = true
|
|
opts[:use_site_subject] = true
|
|
opts[:add_re_to_subject] = true
|
|
opts[:show_category_in_subject] = true
|
|
notification_email(user, opts)
|
|
end
|
|
|
|
def user_private_message(user, opts)
|
|
opts[:allow_reply_by_email] = true
|
|
opts[:use_site_subject] = true
|
|
opts[:add_re_to_subject] = true
|
|
opts[:show_category_in_subject] = false
|
|
|
|
# We use the 'user_posted' event when you are emailed a post in a PM.
|
|
opts[:notification_type] = 'posted'
|
|
|
|
notification_email(user, opts)
|
|
end
|
|
|
|
def user_invited_to_private_message(user, opts)
|
|
opts[:allow_reply_by_email] = false
|
|
opts[:use_invite_template] = true
|
|
notification_email(user, opts)
|
|
end
|
|
|
|
def user_invited_to_topic(user, opts)
|
|
opts[:allow_reply_by_email] = false
|
|
opts[:use_invite_template] = true
|
|
opts[:show_category_in_subject] = true
|
|
notification_email(user, opts)
|
|
end
|
|
|
|
def user_watching_first_post(user, opts)
|
|
user_posted(user, opts)
|
|
end
|
|
|
|
def mailing_list_notify(user, post)
|
|
opts = {
|
|
post: post,
|
|
allow_reply_by_email: true,
|
|
use_site_subject: true,
|
|
add_re_to_subject: true,
|
|
show_category_in_subject: true,
|
|
notification_type: "posted",
|
|
notification_data_hash: {
|
|
original_username: post.user.username,
|
|
topic_title: post.topic.title,
|
|
},
|
|
}
|
|
notification_email(user, opts)
|
|
end
|
|
|
|
protected
|
|
|
|
def user_locale(user)
|
|
(user.locale.present? && I18n.available_locales.include?(user.locale.to_sym)) ? user.locale : nil
|
|
end
|
|
|
|
def email_post_markdown(post, add_posted_by=false)
|
|
result = "[email-indent]\n"
|
|
result << "#{post.raw}\n\n"
|
|
if add_posted_by
|
|
result << "#{I18n.t('user_notifications.posted_by', username: post.username, post_date: post.created_at.strftime("%m/%d/%Y"))}\n\n"
|
|
end
|
|
result << "[/email-indent]\n"
|
|
result
|
|
end
|
|
|
|
class UserNotificationRenderer < ActionView::Base
|
|
include UserNotificationsHelper
|
|
end
|
|
|
|
def self.get_context_posts(post, topic_user, user)
|
|
if user.user_option.email_previous_replies == UserOption.previous_replies_type[:never]
|
|
return []
|
|
end
|
|
|
|
allowed_post_types = [Post.types[:regular]]
|
|
allowed_post_types << Post.types[:whisper] if topic_user.try(:user).try(:staff?)
|
|
|
|
context_posts = Post.where(topic_id: post.topic_id)
|
|
.where("post_number < ?", post.post_number)
|
|
.where(user_deleted: false)
|
|
.where(hidden: false)
|
|
.where(post_type: allowed_post_types)
|
|
.order('created_at desc')
|
|
.limit(SiteSetting.email_posts_context)
|
|
|
|
if topic_user && topic_user.last_emailed_post_number && user.user_option.email_previous_replies == UserOption.previous_replies_type[:unless_emailed]
|
|
context_posts = context_posts.where("post_number > ?", topic_user.last_emailed_post_number)
|
|
end
|
|
|
|
context_posts
|
|
end
|
|
|
|
def notification_email(user, opts)
|
|
notification_type = opts[:notification_type]
|
|
notification_data = opts[:notification_data_hash]
|
|
post = opts[:post]
|
|
|
|
unless String === notification_type
|
|
if Numeric === notification_type
|
|
notification_type = Notification.types[notification_type]
|
|
end
|
|
notification_type = notification_type.to_s
|
|
end
|
|
|
|
user_name = notification_data[:original_username]
|
|
|
|
if post && SiteSetting.enable_names && SiteSetting.display_name_on_email_from
|
|
name = User.where(id: post.user_id).pluck(:name).first
|
|
user_name = name unless name.blank?
|
|
end
|
|
|
|
allow_reply_by_email = opts[:allow_reply_by_email] unless user.suspended?
|
|
original_username = notification_data[:original_username] || notification_data[:display_username]
|
|
|
|
send_notification_email(
|
|
title: notification_data[:topic_title],
|
|
post: post,
|
|
username: original_username,
|
|
from_alias: user_name,
|
|
allow_reply_by_email: allow_reply_by_email,
|
|
use_site_subject: opts[:use_site_subject],
|
|
add_re_to_subject: opts[:add_re_to_subject],
|
|
show_category_in_subject: opts[:show_category_in_subject],
|
|
notification_type: notification_type,
|
|
use_invite_template: opts[:use_invite_template],
|
|
user: user
|
|
)
|
|
end
|
|
|
|
def send_notification_email(opts)
|
|
post = opts[:post]
|
|
title = opts[:title]
|
|
allow_reply_by_email = opts[:allow_reply_by_email]
|
|
use_site_subject = opts[:use_site_subject]
|
|
add_re_to_subject = opts[:add_re_to_subject] && post.post_number > 1
|
|
username = opts[:username]
|
|
from_alias = opts[:from_alias]
|
|
notification_type = opts[:notification_type]
|
|
user = opts[:user]
|
|
locale = user_locale(user)
|
|
|
|
template = "user_notifications.user_#{notification_type}"
|
|
if post.topic.private_message?
|
|
template << "_pm"
|
|
template << "_staged" if user.staged?
|
|
end
|
|
|
|
# category name
|
|
category = Topic.find_by(id: post.topic_id).category
|
|
if opts[:show_category_in_subject] && post.topic_id && category && !category.uncategorized?
|
|
show_category_in_subject = category.name
|
|
|
|
# subcategory case
|
|
if !category.parent_category_id.nil?
|
|
show_category_in_subject = "#{Category.find_by(id: category.parent_category_id).name}/#{show_category_in_subject}"
|
|
end
|
|
else
|
|
show_category_in_subject = nil
|
|
end
|
|
|
|
context = ""
|
|
tu = TopicUser.get(post.topic_id, user)
|
|
context_posts = self.class.get_context_posts(post, tu, user)
|
|
|
|
# make .present? cheaper
|
|
context_posts = context_posts.to_a
|
|
|
|
if context_posts.present?
|
|
context << "-- \n*#{I18n.t('user_notifications.previous_discussion')}*\n"
|
|
context_posts.each do |cp|
|
|
context << email_post_markdown(cp, true)
|
|
end
|
|
end
|
|
|
|
translation_override_exists = TranslationOverride.where(locale: SiteSetting.default_locale, translation_key: "#{template}.text_body_template").exists?
|
|
|
|
if opts[:use_invite_template]
|
|
if post.topic.private_message?
|
|
invite_template = "user_notifications.invited_to_private_message_body"
|
|
else
|
|
invite_template = "user_notifications.invited_to_topic_body"
|
|
end
|
|
topic_excerpt = post.excerpt.tr("\n", " ") if post.is_first_post? && post.excerpt
|
|
message = I18n.t(invite_template, username: username, topic_title: gsub_emoji_to_unicode(title), topic_excerpt: topic_excerpt, site_title: SiteSetting.title, site_description: SiteSetting.site_description)
|
|
|
|
unless translation_override_exists
|
|
html = UserNotificationRenderer.new(Rails.configuration.paths["app/views"]).render(
|
|
template: 'email/invite',
|
|
format: :html,
|
|
locals: { message: PrettyText.cook(message, sanitize: false).html_safe,
|
|
classes: RTL.new(user).css_class
|
|
}
|
|
)
|
|
end
|
|
else
|
|
reached_limit = SiteSetting.max_emails_per_day_per_user > 0
|
|
reached_limit &&= (EmailLog.where(user_id: user.id, skipped: false)
|
|
.where('created_at > ?', 1.day.ago)
|
|
.count) >= (SiteSetting.max_emails_per_day_per_user-1)
|
|
|
|
in_reply_to_post = post.reply_to_post if user.user_option.email_in_reply_to
|
|
message = email_post_markdown(post) + (reached_limit ? "\n\n#{I18n.t "user_notifications.reached_limit", count: SiteSetting.max_emails_per_day_per_user}" : "");
|
|
|
|
unless translation_override_exists
|
|
html = UserNotificationRenderer.new(Rails.configuration.paths["app/views"]).render(
|
|
template: 'email/notification',
|
|
format: :html,
|
|
locals: { context_posts: context_posts,
|
|
reached_limit: reached_limit,
|
|
post: post,
|
|
in_reply_to_post: in_reply_to_post,
|
|
classes: RTL.new(user).css_class
|
|
}
|
|
)
|
|
end
|
|
end
|
|
|
|
email_opts = {
|
|
topic_title: gsub_emoji_to_unicode(title),
|
|
topic_title_url_encoded: title ? URI.encode(title) : title,
|
|
message: message,
|
|
url: post.url,
|
|
post_id: post.id,
|
|
topic_id: post.topic_id,
|
|
context: context,
|
|
username: username,
|
|
add_unsubscribe_link: !user.staged,
|
|
mailing_list_mode: user.user_option.mailing_list_mode,
|
|
unsubscribe_url: post.unsubscribe_url(user),
|
|
allow_reply_by_email: allow_reply_by_email,
|
|
only_reply_by_email: allow_reply_by_email && user.staged,
|
|
use_site_subject: use_site_subject,
|
|
add_re_to_subject: add_re_to_subject,
|
|
show_category_in_subject: show_category_in_subject,
|
|
private_reply: post.topic.private_message?,
|
|
include_respond_instructions: !(user.suspended? || user.staged?),
|
|
template: template,
|
|
site_description: SiteSetting.site_description,
|
|
site_title: SiteSetting.title,
|
|
site_title_url_encoded: URI.encode(SiteSetting.title),
|
|
style: :notification,
|
|
locale: locale
|
|
}
|
|
|
|
unless translation_override_exists
|
|
email_opts[:html_override] = html
|
|
end
|
|
|
|
# If we have a display name, change the from address
|
|
email_opts[:from_alias] = from_alias if from_alias.present?
|
|
|
|
TopicUser.change(user.id, post.topic_id, last_emailed_post_number: post.post_number)
|
|
|
|
build_email(user.email, email_opts)
|
|
end
|
|
|
|
private
|
|
|
|
def build_summary_for(user)
|
|
@user = user
|
|
@date = short_date(Time.now)
|
|
@base_url = Discourse.base_url
|
|
@email_prefix = SiteSetting.email_prefix.presence || SiteSetting.title
|
|
@header_color = ColorScheme.hex_for_name('header_primary')
|
|
@header_bgcolor = ColorScheme.hex_for_name('header_background')
|
|
@anchor_color = ColorScheme.hex_for_name('tertiary')
|
|
@markdown_linker = MarkdownLinker.new(@base_url)
|
|
@unsubscribe_key = UnsubscribeKey.create_key_for(@user, "digest")
|
|
end
|
|
|
|
def apply_notification_styles(email)
|
|
email.html_part.body = Email::Styles.new(email.html_part.body.to_s).tap do |styles|
|
|
styles.format_basic
|
|
styles.format_notification
|
|
end.to_html
|
|
email
|
|
end
|
|
end
|