mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-30 10:58:31 -05:00
Unsubscribe via email
This commit is contained in:
parent
11ea16a91a
commit
c7283751a3
13 changed files with 159 additions and 5 deletions
|
@ -11,6 +11,7 @@ class EmailController < ApplicationController
|
||||||
|
|
||||||
def unsubscribe
|
def unsubscribe
|
||||||
@user = DigestUnsubscribeKey.user_for_key(params[:key])
|
@user = DigestUnsubscribeKey.user_for_key(params[:key])
|
||||||
|
RateLimiter.new(@user, "unsubscribe_via_email", 3, 1.day).performed! unless @user && @user.staff?
|
||||||
|
|
||||||
# Don't allow the use of a key while logged in as a different user
|
# Don't allow the use of a key while logged in as a different user
|
||||||
if current_user.present? && (@user != current_user)
|
if current_user.present? && (@user != current_user)
|
||||||
|
@ -23,7 +24,12 @@ class EmailController < ApplicationController
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@user.update_column(:email_digests, false)
|
if params[:from_all]
|
||||||
|
@user.update_columns(email_digests: false, email_direct: false, email_private_messages: false, email_always: false)
|
||||||
|
else
|
||||||
|
@user.update_column(:email_digests, false)
|
||||||
|
end
|
||||||
|
|
||||||
@success = true
|
@success = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
14
app/mailers/subscription_mailer.rb
Normal file
14
app/mailers/subscription_mailer.rb
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
require_dependency 'email/message_builder'
|
||||||
|
|
||||||
|
class SubscriptionMailer < ActionMailer::Base
|
||||||
|
include Email::BuildEmailHelper
|
||||||
|
|
||||||
|
def confirm_unsubscribe(user, opts={})
|
||||||
|
unsubscribe_key = DigestUnsubscribeKey.create_key_for(user)
|
||||||
|
build_email user.email,
|
||||||
|
template: "unsubscribe_mailer",
|
||||||
|
site_title: SiteSetting.title,
|
||||||
|
site_domain_name: Discourse.current_hostname,
|
||||||
|
confirm_unsubscribe_link: "#{Discourse.base_url}/unsubscribe/#{unsubscribe_key}?from_all=true"
|
||||||
|
end
|
||||||
|
end
|
|
@ -307,6 +307,7 @@ class UserNotifications < ActionMailer::Base
|
||||||
context: context,
|
context: context,
|
||||||
username: username,
|
username: username,
|
||||||
add_unsubscribe_link: !user.staged,
|
add_unsubscribe_link: !user.staged,
|
||||||
|
add_unsubscribe_via_email_link: user.mailing_list_mode,
|
||||||
unsubscribe_url: post.topic.unsubscribe_url,
|
unsubscribe_url: post.topic.unsubscribe_url,
|
||||||
allow_reply_by_email: allow_reply_by_email,
|
allow_reply_by_email: allow_reply_by_email,
|
||||||
use_site_subject: use_site_subject,
|
use_site_subject: use_site_subject,
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class='footer'>%{respond_instructions}</div>
|
<div class='footer'>%{respond_instructions}</div>
|
||||||
<div class='footer'>%{unsubscribe_link}</div>
|
<div class='footer'>%{unsubscribe_link}%{unsubscribe_via_email_link}</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1104,6 +1104,9 @@ en:
|
||||||
short_email_length: "Short email length in Bytes"
|
short_email_length: "Short email length in Bytes"
|
||||||
display_name_on_email_from: "Display full names on email from fields"
|
display_name_on_email_from: "Display full names on email from fields"
|
||||||
|
|
||||||
|
unsubscribe_via_email: "Allow users to unsubscribe from emails by sending an email with 'unsubscribe' in the subject or body"
|
||||||
|
unsubscribe_via_email_footer: "Attach an unsubscribe link to the footer of sent emails"
|
||||||
|
|
||||||
pop3_polling_enabled: "Poll via POP3 for email replies."
|
pop3_polling_enabled: "Poll via POP3 for email replies."
|
||||||
pop3_polling_ssl: "Use SSL while connecting to the POP3 server. (Recommended)"
|
pop3_polling_ssl: "Use SSL while connecting to the POP3 server. (Recommended)"
|
||||||
pop3_polling_period_mins: "The period in minutes between checking the POP3 account for email. NOTE: requires restart."
|
pop3_polling_period_mins: "The period in minutes between checking the POP3 account for email. NOTE: requires restart."
|
||||||
|
@ -1379,6 +1382,17 @@ en:
|
||||||
blocked: "New registrations are not allowed from your IP address."
|
blocked: "New registrations are not allowed from your IP address."
|
||||||
max_new_accounts_per_registration_ip: "New registrations are not allowed from your IP address (maximum limit reached). Contact a staff member."
|
max_new_accounts_per_registration_ip: "New registrations are not allowed from your IP address (maximum limit reached). Contact a staff member."
|
||||||
|
|
||||||
|
unsubscribe_mailer:
|
||||||
|
subject_template: "Confirm you no longer want to receive email updates from %{site_title}"
|
||||||
|
text_body_template: |
|
||||||
|
Someone (possibly you?) requested to no longer send email updates from %{site_domain_name} to this address.
|
||||||
|
If you with to confirm this, please click this link:
|
||||||
|
|
||||||
|
%{confirm_unsubscribe_link}
|
||||||
|
|
||||||
|
|
||||||
|
I you want to continue receiving email updates, you may ignore this email.
|
||||||
|
|
||||||
invite_mailer:
|
invite_mailer:
|
||||||
subject_template: "%{invitee_name} invited you to '%{topic_title}' on %{site_domain_name}"
|
subject_template: "%{invitee_name} invited you to '%{topic_title}' on %{site_domain_name}"
|
||||||
text_body_template: |
|
text_body_template: |
|
||||||
|
@ -1940,7 +1954,10 @@ en:
|
||||||
text_body_template: "The `download_remote_images_to_local` setting was disabled because the disk space limit at `download_remote_images_threshold` was reached."
|
text_body_template: "The `download_remote_images_to_local` setting was disabled because the disk space limit at `download_remote_images_threshold` was reached."
|
||||||
|
|
||||||
unsubscribe_link: |
|
unsubscribe_link: |
|
||||||
To stop receiving notifications for this particular topic, [click here](%{unsubscribe_url}). To unsubscribe from these emails, change your [user preferences](%{user_preferences_url}).
|
To stop receiving notifications for this particular topic, [click here](%{unsubscribe_url}). To unsubscribe from these emails, change your [user preferences](%{user_preferences_url})
|
||||||
|
|
||||||
|
unsubscribe_via_email_link: |
|
||||||
|
or, [click here](mailto:reply@%{hostname}?subject=unsubscribe) to unsubscribe via email.
|
||||||
|
|
||||||
subject_re: "Re: "
|
subject_re: "Re: "
|
||||||
subject_pm: "[PM] "
|
subject_pm: "[PM] "
|
||||||
|
|
|
@ -535,6 +535,10 @@ email:
|
||||||
short_email_length: 2800
|
short_email_length: 2800
|
||||||
display_name_on_email_from:
|
display_name_on_email_from:
|
||||||
default: true
|
default: true
|
||||||
|
unsubscribe_via_email:
|
||||||
|
default: true
|
||||||
|
unsubscribe_via_email_footer:
|
||||||
|
default: false
|
||||||
|
|
||||||
files:
|
files:
|
||||||
max_image_size_kb: 3072
|
max_image_size_kb: 3072
|
||||||
|
|
|
@ -63,6 +63,13 @@ module Email
|
||||||
if @opts[:add_unsubscribe_link]
|
if @opts[:add_unsubscribe_link]
|
||||||
unsubscribe_link = PrettyText.cook(I18n.t('unsubscribe_link', template_args), sanitize: false).html_safe
|
unsubscribe_link = PrettyText.cook(I18n.t('unsubscribe_link', template_args), sanitize: false).html_safe
|
||||||
html_override.gsub!("%{unsubscribe_link}", unsubscribe_link)
|
html_override.gsub!("%{unsubscribe_link}", unsubscribe_link)
|
||||||
|
|
||||||
|
if SiteSetting.unsubscribe_via_email_footer && @opts[:add_unsubscribe_via_email_link]
|
||||||
|
unsubscribe_via_email_link = PrettyText.cook(I18n.t('unsubscribe_via_email_link', hostname: Discourse.current_hostname), sanitize: false).html_safe
|
||||||
|
html_override.gsub!("%{unsubscribe_via_email_link}", unsubscribe_via_email_link)
|
||||||
|
else
|
||||||
|
html_override.gsub!("%{unsubscribe_via_email_link}", "")
|
||||||
|
end
|
||||||
else
|
else
|
||||||
html_override.gsub!("%{unsubscribe_link}", "")
|
html_override.gsub!("%{unsubscribe_link}", "")
|
||||||
end
|
end
|
||||||
|
@ -103,6 +110,9 @@ module Email
|
||||||
if @opts[:add_unsubscribe_link]
|
if @opts[:add_unsubscribe_link]
|
||||||
body << "\n"
|
body << "\n"
|
||||||
body << I18n.t('unsubscribe_link', template_args)
|
body << I18n.t('unsubscribe_link', template_args)
|
||||||
|
if SiteSetting.unsubscribe_via_email_footer && @opts[:add_unsubscribe_via_email_link]
|
||||||
|
body << I18n.t('unsubscribe_via_email_link', hostname: Discourse.current_hostname)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
body
|
body
|
||||||
|
|
|
@ -59,7 +59,10 @@ module Email
|
||||||
|
|
||||||
raise InactiveUserError if !user.active && !user.staged
|
raise InactiveUserError if !user.active && !user.staged
|
||||||
|
|
||||||
if post = find_related_post
|
if action = subscription_action_for(body, @mail.subject)
|
||||||
|
message = SubscriptionMailer.send(action, user)
|
||||||
|
Email::Sender.new(message, :subscription).send
|
||||||
|
elsif post = find_related_post
|
||||||
create_reply(user: user, raw: body, post: post, topic: post.topic)
|
create_reply(user: user, raw: body, post: post, topic: post.topic)
|
||||||
else
|
else
|
||||||
destination = destinations.first
|
destination = destinations.first
|
||||||
|
@ -226,6 +229,13 @@ module Email
|
||||||
@likes ||= Set.new ["+1", I18n.t('post_action_types.like.title').downcase]
|
@likes ||= Set.new ["+1", I18n.t('post_action_types.like.title').downcase]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def subscription_action_for(body, subject)
|
||||||
|
return unless SiteSetting.unsubscribe_via_email
|
||||||
|
if ([subject, body].compact.map(&:to_s).map(&:downcase) & ['unsubscribe']).any?
|
||||||
|
:confirm_unsubscribe
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def post_action_for(body)
|
def post_action_for(body)
|
||||||
if likes.include?(body.strip.downcase)
|
if likes.include?(body.strip.downcase)
|
||||||
PostActionType.types[:like]
|
PostActionType.types[:like]
|
||||||
|
|
|
@ -181,6 +181,23 @@ describe Email::MessageBuilder do
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with unsubscribe_via_email_link true" do
|
||||||
|
let(:message_with_unsubscribe_via_email) { Email::MessageBuilder.new(to_address,
|
||||||
|
body: 'hello world',
|
||||||
|
add_unsubscribe_link: true,
|
||||||
|
add_unsubscribe_via_email_link: true,
|
||||||
|
unsubscribe_url: "/t/1234/unsubscribe") }
|
||||||
|
|
||||||
|
it "can add an unsubscribe via email link" do
|
||||||
|
SiteSetting.stubs(:unsubscribe_via_email_footer).returns(true)
|
||||||
|
expect(message_with_unsubscribe_via_email.body).to match(/mailto:reply@#{Discourse.current_hostname}\?subject=unsubscribe/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not add unsubscribe via email link without site setting set" do
|
||||||
|
expect(message_with_unsubscribe_via_email.body).to_not match(/mailto:reply@#{Discourse.current_hostname}\?subject=unsubscribe/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "template_args" do
|
context "template_args" do
|
||||||
|
|
|
@ -130,6 +130,46 @@ describe Email::Receiver do
|
||||||
expect(topic.posts.last.raw).to eq("Do you like liquorice?\n\nI really like them. One could even say that I am *addicted* to liquorice. Anf if\nyou can mix it up with some anise, then I'm in heaven ;)")
|
expect(topic.posts.last.raw).to eq("Do you like liquorice?\n\nI really like them. One could even say that I am *addicted* to liquorice. Anf if\nyou can mix it up with some anise, then I'm in heaven ;)")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'Unsubscribing via email' do
|
||||||
|
let(:last_email) { ActionMailer::Base.deliveries.last }
|
||||||
|
|
||||||
|
describe 'unsubscribe_subject.eml' do
|
||||||
|
it 'sends an email asking the user to confirm the unsubscription' do
|
||||||
|
expect { process("unsubscribe_subject") }.to change { ActionMailer::Base.deliveries.count }.by(1)
|
||||||
|
expect(last_email.to.length).to eq 1
|
||||||
|
expect(last_email.from.length).to eq 1
|
||||||
|
expect(last_email.from).to include "noreply@#{Discourse.current_hostname}"
|
||||||
|
expect(last_email.to).to include "discourse@bar.com"
|
||||||
|
expect(last_email.subject).to eq I18n.t(:"unsubscribe_mailer.subject_template").gsub("%{site_title}", SiteSetting.title)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does nothing unless unsubscribe_via_email is turned on' do
|
||||||
|
SiteSetting.stubs("unsubscribe_via_email").returns(false)
|
||||||
|
before_deliveries = ActionMailer::Base.deliveries.count
|
||||||
|
expect { process("unsubscribe_subject") }.to raise_error { Email::Receiver::BadDestinationAddress }
|
||||||
|
expect(before_deliveries).to eq ActionMailer::Base.deliveries.count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'unsubscribe_body.eml' do
|
||||||
|
it 'sends an email asking the user to confirm the unsubscription' do
|
||||||
|
expect { process("unsubscribe_body") }.to change { ActionMailer::Base.deliveries.count }.by(1)
|
||||||
|
expect(last_email.to.length).to eq 1
|
||||||
|
expect(last_email.from.length).to eq 1
|
||||||
|
expect(last_email.from).to include "noreply@#{Discourse.current_hostname}"
|
||||||
|
expect(last_email.to).to include "discourse@bar.com"
|
||||||
|
expect(last_email.subject).to eq I18n.t(:"unsubscribe_mailer.subject_template").gsub("%{site_title}", SiteSetting.title)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does nothing unless unsubscribe_via_email is turned on' do
|
||||||
|
SiteSetting.stubs(:unsubscribe_via_email).returns(false)
|
||||||
|
before_deliveries = ActionMailer::Base.deliveries.count
|
||||||
|
expect { process("unsubscribe_body") }.to raise_error { Email::Receiver::InvalidPost }
|
||||||
|
expect(before_deliveries).to eq ActionMailer::Base.deliveries.count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it "handles inline reply" do
|
it "handles inline reply" do
|
||||||
expect { process(:inline_reply) }.to change { topic.posts.count }
|
expect { process(:inline_reply) }.to change { topic.posts.count }
|
||||||
expect(topic.posts.last.raw).to eq("On Tue, Jan 15, 2016 at 11:12 AM, Bar Foo <info@unconfigured.discourse.org> wrote:\n\n> WAT <https://bar.com/users/wat> November 28\n>\n> This is the previous post.\n\nAnd this is *my* reply :+1:")
|
expect(topic.posts.last.raw).to eq("On Tue, Jan 15, 2016 at 11:12 AM, Bar Foo <info@unconfigured.discourse.org> wrote:\n\n> WAT <https://bar.com/users/wat> November 28\n>\n> This is the previous post.\n\nAnd this is *my* reply :+1:")
|
||||||
|
|
|
@ -39,9 +39,23 @@ describe EmailController do
|
||||||
|
|
||||||
context '.unsubscribe' do
|
context '.unsubscribe' do
|
||||||
|
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user, email_digests: true, email_direct: true, email_private_messages: true, email_always: true) }
|
||||||
let(:key) { DigestUnsubscribeKey.create_key_for(user) }
|
let(:key) { DigestUnsubscribeKey.create_key_for(user) }
|
||||||
|
|
||||||
|
context 'from confirm unsubscribe email' do
|
||||||
|
before do
|
||||||
|
get :unsubscribe, key: key, from_all: true
|
||||||
|
user.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'unsubscribes from all emails' do
|
||||||
|
expect(user.email_digests).to eq false
|
||||||
|
expect(user.email_direct).to eq false
|
||||||
|
expect(user.email_private_messages).to eq false
|
||||||
|
expect(user.email_always).to eq false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with a valid key' do
|
context 'with a valid key' do
|
||||||
before do
|
before do
|
||||||
get :unsubscribe, key: key
|
get :unsubscribe, key: key
|
||||||
|
|
10
spec/fixtures/emails/unsubscribe_body.eml
vendored
Normal file
10
spec/fixtures/emails/unsubscribe_body.eml
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
Return-Path: <discourse@bar.com>
|
||||||
|
From: Foo Bar <discourse@bar.com>
|
||||||
|
To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com
|
||||||
|
Date: Thu, 13 Jun 2013 17:03:48 -0400
|
||||||
|
Message-ID: <55@foo.bar.mail>
|
||||||
|
Mime-Version: 1.0
|
||||||
|
Content-Type: text/plain;
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
UNSUBSCRIBE
|
11
spec/fixtures/emails/unsubscribe_subject.eml
vendored
Normal file
11
spec/fixtures/emails/unsubscribe_subject.eml
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Return-Path: <discourse@bar.com>
|
||||||
|
From: Foo Bar <discourse@bar.com>
|
||||||
|
To: reply@bar.com
|
||||||
|
Date: Thu, 13 Jun 2013 17:03:48 -0400
|
||||||
|
Message-ID: <56@foo.bar.mail>
|
||||||
|
Subject: UnSuBScRiBe
|
||||||
|
Mime-Version: 1.0
|
||||||
|
Content-Type: text/plain;
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
I've basically had enough of your mailing list and would very much like it if you went away.
|
Loading…
Reference in a new issue