##// END OF EJS Templates
Adds Message-Id and References headers to email notifications so that issues and messages threads can be displayed by email clients (#1401)....
Jean-Philippe Lang -
r2279:1d783106a34b
parent child
Show More
@@ -1,253 +1,297
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Mailer < ActionMailer::Base
18 class Mailer < ActionMailer::Base
19 helper :application
19 helper :application
20 helper :issues
20 helper :issues
21 helper :custom_fields
21 helper :custom_fields
22
22
23 include ActionController::UrlWriter
23 include ActionController::UrlWriter
24
24
25 def issue_add(issue)
25 def issue_add(issue)
26 redmine_headers 'Project' => issue.project.identifier,
26 redmine_headers 'Project' => issue.project.identifier,
27 'Issue-Id' => issue.id,
27 'Issue-Id' => issue.id,
28 'Issue-Author' => issue.author.login
28 'Issue-Author' => issue.author.login
29 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
29 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
30 message_id issue
30 recipients issue.recipients
31 recipients issue.recipients
31 cc(issue.watcher_recipients - @recipients)
32 cc(issue.watcher_recipients - @recipients)
32 subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
33 subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
33 body :issue => issue,
34 body :issue => issue,
34 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
35 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
35 end
36 end
36
37
37 def issue_edit(journal)
38 def issue_edit(journal)
38 issue = journal.journalized
39 issue = journal.journalized
39 redmine_headers 'Project' => issue.project.identifier,
40 redmine_headers 'Project' => issue.project.identifier,
40 'Issue-Id' => issue.id,
41 'Issue-Id' => issue.id,
41 'Issue-Author' => issue.author.login
42 'Issue-Author' => issue.author.login
42 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
43 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
44 message_id journal
45 references issue
43 @author = journal.user
46 @author = journal.user
44 recipients issue.recipients
47 recipients issue.recipients
45 # Watchers in cc
48 # Watchers in cc
46 cc(issue.watcher_recipients - @recipients)
49 cc(issue.watcher_recipients - @recipients)
47 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
50 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
48 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
51 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
49 s << issue.subject
52 s << issue.subject
50 subject s
53 subject s
51 body :issue => issue,
54 body :issue => issue,
52 :journal => journal,
55 :journal => journal,
53 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
56 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
54 end
57 end
55
58
56 def reminder(user, issues, days)
59 def reminder(user, issues, days)
57 set_language_if_valid user.language
60 set_language_if_valid user.language
58 recipients user.mail
61 recipients user.mail
59 subject l(:mail_subject_reminder, issues.size)
62 subject l(:mail_subject_reminder, issues.size)
60 body :issues => issues,
63 body :issues => issues,
61 :days => days,
64 :days => days,
62 :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc')
65 :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc')
63 end
66 end
64
67
65 def document_added(document)
68 def document_added(document)
66 redmine_headers 'Project' => document.project.identifier
69 redmine_headers 'Project' => document.project.identifier
67 recipients document.project.recipients
70 recipients document.project.recipients
68 subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
71 subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
69 body :document => document,
72 body :document => document,
70 :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
73 :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
71 end
74 end
72
75
73 def attachments_added(attachments)
76 def attachments_added(attachments)
74 container = attachments.first.container
77 container = attachments.first.container
75 added_to = ''
78 added_to = ''
76 added_to_url = ''
79 added_to_url = ''
77 case container.class.name
80 case container.class.name
78 when 'Project'
81 when 'Project'
79 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container)
82 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container)
80 added_to = "#{l(:label_project)}: #{container}"
83 added_to = "#{l(:label_project)}: #{container}"
81 when 'Version'
84 when 'Version'
82 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
85 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
83 added_to = "#{l(:label_version)}: #{container.name}"
86 added_to = "#{l(:label_version)}: #{container.name}"
84 when 'Document'
87 when 'Document'
85 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
88 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
86 added_to = "#{l(:label_document)}: #{container.title}"
89 added_to = "#{l(:label_document)}: #{container.title}"
87 end
90 end
88 redmine_headers 'Project' => container.project.identifier
91 redmine_headers 'Project' => container.project.identifier
89 recipients container.project.recipients
92 recipients container.project.recipients
90 subject "[#{container.project.name}] #{l(:label_attachment_new)}"
93 subject "[#{container.project.name}] #{l(:label_attachment_new)}"
91 body :attachments => attachments,
94 body :attachments => attachments,
92 :added_to => added_to,
95 :added_to => added_to,
93 :added_to_url => added_to_url
96 :added_to_url => added_to_url
94 end
97 end
95
98
96 def news_added(news)
99 def news_added(news)
97 redmine_headers 'Project' => news.project.identifier
100 redmine_headers 'Project' => news.project.identifier
101 message_id news
98 recipients news.project.recipients
102 recipients news.project.recipients
99 subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
103 subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
100 body :news => news,
104 body :news => news,
101 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
105 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
102 end
106 end
103
107
104 def message_posted(message, recipients)
108 def message_posted(message, recipients)
105 redmine_headers 'Project' => message.project.identifier,
109 redmine_headers 'Project' => message.project.identifier,
106 'Topic-Id' => (message.parent_id || message.id)
110 'Topic-Id' => (message.parent_id || message.id)
111 message_id message
112 references message.parent unless message.parent.nil?
107 recipients(recipients)
113 recipients(recipients)
108 subject "[#{message.board.project.name} - #{message.board.name}] #{message.subject}"
114 subject "[#{message.board.project.name} - #{message.board.name}] #{message.subject}"
109 body :message => message,
115 body :message => message,
110 :message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root)
116 :message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root)
111 end
117 end
112
118
113 def account_information(user, password)
119 def account_information(user, password)
114 set_language_if_valid user.language
120 set_language_if_valid user.language
115 recipients user.mail
121 recipients user.mail
116 subject l(:mail_subject_register, Setting.app_title)
122 subject l(:mail_subject_register, Setting.app_title)
117 body :user => user,
123 body :user => user,
118 :password => password,
124 :password => password,
119 :login_url => url_for(:controller => 'account', :action => 'login')
125 :login_url => url_for(:controller => 'account', :action => 'login')
120 end
126 end
121
127
122 def account_activation_request(user)
128 def account_activation_request(user)
123 # Send the email to all active administrators
129 # Send the email to all active administrators
124 recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
130 recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
125 subject l(:mail_subject_account_activation_request, Setting.app_title)
131 subject l(:mail_subject_account_activation_request, Setting.app_title)
126 body :user => user,
132 body :user => user,
127 :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
133 :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
128 end
134 end
129
135
130 def lost_password(token)
136 def lost_password(token)
131 set_language_if_valid(token.user.language)
137 set_language_if_valid(token.user.language)
132 recipients token.user.mail
138 recipients token.user.mail
133 subject l(:mail_subject_lost_password, Setting.app_title)
139 subject l(:mail_subject_lost_password, Setting.app_title)
134 body :token => token,
140 body :token => token,
135 :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
141 :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
136 end
142 end
137
143
138 def register(token)
144 def register(token)
139 set_language_if_valid(token.user.language)
145 set_language_if_valid(token.user.language)
140 recipients token.user.mail
146 recipients token.user.mail
141 subject l(:mail_subject_register, Setting.app_title)
147 subject l(:mail_subject_register, Setting.app_title)
142 body :token => token,
148 body :token => token,
143 :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
149 :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
144 end
150 end
145
151
146 def test(user)
152 def test(user)
147 set_language_if_valid(user.language)
153 set_language_if_valid(user.language)
148 recipients user.mail
154 recipients user.mail
149 subject 'Redmine test'
155 subject 'Redmine test'
150 body :url => url_for(:controller => 'welcome')
156 body :url => url_for(:controller => 'welcome')
151 end
157 end
152
158
153 # Overrides default deliver! method to prevent from sending an email
159 # Overrides default deliver! method to prevent from sending an email
154 # with no recipient, cc or bcc
160 # with no recipient, cc or bcc
155 def deliver!(mail = @mail)
161 def deliver!(mail = @mail)
156 return false if (recipients.nil? || recipients.empty?) &&
162 return false if (recipients.nil? || recipients.empty?) &&
157 (cc.nil? || cc.empty?) &&
163 (cc.nil? || cc.empty?) &&
158 (bcc.nil? || bcc.empty?)
164 (bcc.nil? || bcc.empty?)
159 super
165
166 # Set Message-Id and References
167 if @message_id_object
168 mail.message_id = self.class.message_id_for(@message_id_object)
169 end
170 if @references_objects
171 mail.references = @references_objects.collect {|o| self.class.message_id_for(o)}
172 end
173 super(mail)
160 end
174 end
161
175
162 # Sends reminders to issue assignees
176 # Sends reminders to issue assignees
163 # Available options:
177 # Available options:
164 # * :days => how many days in the future to remind about (defaults to 7)
178 # * :days => how many days in the future to remind about (defaults to 7)
165 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
179 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
166 # * :project => id or identifier of project to process (defaults to all projects)
180 # * :project => id or identifier of project to process (defaults to all projects)
167 def self.reminders(options={})
181 def self.reminders(options={})
168 days = options[:days] || 7
182 days = options[:days] || 7
169 project = options[:project] ? Project.find(options[:project]) : nil
183 project = options[:project] ? Project.find(options[:project]) : nil
170 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
184 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
171
185
172 s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date]
186 s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date]
173 s << "#{Issue.table_name}.assigned_to_id IS NOT NULL"
187 s << "#{Issue.table_name}.assigned_to_id IS NOT NULL"
174 s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
188 s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
175 s << "#{Issue.table_name}.project_id = #{project.id}" if project
189 s << "#{Issue.table_name}.project_id = #{project.id}" if project
176 s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
190 s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
177
191
178 issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker],
192 issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker],
179 :conditions => s.conditions
193 :conditions => s.conditions
180 ).group_by(&:assigned_to)
194 ).group_by(&:assigned_to)
181 issues_by_assignee.each do |assignee, issues|
195 issues_by_assignee.each do |assignee, issues|
182 deliver_reminder(assignee, issues, days) unless assignee.nil?
196 deliver_reminder(assignee, issues, days) unless assignee.nil?
183 end
197 end
184 end
198 end
185
199
186 private
200 private
187 def initialize_defaults(method_name)
201 def initialize_defaults(method_name)
188 super
202 super
189 set_language_if_valid Setting.default_language
203 set_language_if_valid Setting.default_language
190 from Setting.mail_from
204 from Setting.mail_from
191
205
192 # URL options
206 # URL options
193 h = Setting.host_name
207 h = Setting.host_name
194 h = h.to_s.gsub(%r{\/.*$}, '') unless Redmine::Utils.relative_url_root.blank?
208 h = h.to_s.gsub(%r{\/.*$}, '') unless Redmine::Utils.relative_url_root.blank?
195 default_url_options[:host] = h
209 default_url_options[:host] = h
196 default_url_options[:protocol] = Setting.protocol
210 default_url_options[:protocol] = Setting.protocol
197
211
198 # Common headers
212 # Common headers
199 headers 'X-Mailer' => 'Redmine',
213 headers 'X-Mailer' => 'Redmine',
200 'X-Redmine-Host' => Setting.host_name,
214 'X-Redmine-Host' => Setting.host_name,
201 'X-Redmine-Site' => Setting.app_title
215 'X-Redmine-Site' => Setting.app_title
202 end
216 end
203
217
204 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
218 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
205 def redmine_headers(h)
219 def redmine_headers(h)
206 h.each { |k,v| headers["X-Redmine-#{k}"] = v }
220 h.each { |k,v| headers["X-Redmine-#{k}"] = v }
207 end
221 end
208
222
209 # Overrides the create_mail method
223 # Overrides the create_mail method
210 def create_mail
224 def create_mail
211 # Removes the current user from the recipients and cc
225 # Removes the current user from the recipients and cc
212 # if he doesn't want to receive notifications about what he does
226 # if he doesn't want to receive notifications about what he does
213 @author ||= User.current
227 @author ||= User.current
214 if @author.pref[:no_self_notified]
228 if @author.pref[:no_self_notified]
215 recipients.delete(@author.mail) if recipients
229 recipients.delete(@author.mail) if recipients
216 cc.delete(@author.mail) if cc
230 cc.delete(@author.mail) if cc
217 end
231 end
218 # Blind carbon copy recipients
232 # Blind carbon copy recipients
219 if Setting.bcc_recipients?
233 if Setting.bcc_recipients?
220 bcc([recipients, cc].flatten.compact.uniq)
234 bcc([recipients, cc].flatten.compact.uniq)
221 recipients []
235 recipients []
222 cc []
236 cc []
223 end
237 end
224 super
238 super
225 end
239 end
226
240
227 # Renders a message with the corresponding layout
241 # Renders a message with the corresponding layout
228 def render_message(method_name, body)
242 def render_message(method_name, body)
229 layout = method_name.to_s.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml'
243 layout = method_name.to_s.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml'
230 body[:content_for_layout] = render(:file => method_name, :body => body)
244 body[:content_for_layout] = render(:file => method_name, :body => body)
231 ActionView::Base.new(template_root, body, self).render(:file => "mailer/#{layout}", :use_full_path => true)
245 ActionView::Base.new(template_root, body, self).render(:file => "mailer/#{layout}", :use_full_path => true)
232 end
246 end
233
247
234 # for the case of plain text only
248 # for the case of plain text only
235 def body(*params)
249 def body(*params)
236 value = super(*params)
250 value = super(*params)
237 if Setting.plain_text_mail?
251 if Setting.plain_text_mail?
238 templates = Dir.glob("#{template_path}/#{@template}.text.plain.{rhtml,erb}")
252 templates = Dir.glob("#{template_path}/#{@template}.text.plain.{rhtml,erb}")
239 unless String === @body or templates.empty?
253 unless String === @body or templates.empty?
240 template = File.basename(templates.first)
254 template = File.basename(templates.first)
241 @body[:content_for_layout] = render(:file => template, :body => @body)
255 @body[:content_for_layout] = render(:file => template, :body => @body)
242 @body = ActionView::Base.new(template_root, @body, self).render(:file => "mailer/layout.text.plain.rhtml", :use_full_path => true)
256 @body = ActionView::Base.new(template_root, @body, self).render(:file => "mailer/layout.text.plain.rhtml", :use_full_path => true)
243 return @body
257 return @body
244 end
258 end
245 end
259 end
246 return value
260 return value
247 end
261 end
248
262
249 # Makes partial rendering work with Rails 1.2 (retro-compatibility)
263 # Makes partial rendering work with Rails 1.2 (retro-compatibility)
250 def self.controller_path
264 def self.controller_path
251 ''
265 ''
252 end unless respond_to?('controller_path')
266 end unless respond_to?('controller_path')
267
268 # Returns a predictable Message-Id for the given object
269 def self.message_id_for(object)
270 # id + timestamp should reduce the odds of a collision
271 # as far as we don't send multiple emails for the same object
272 hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{object.created_on.strftime("%Y%m%d%H%M%S")}"
273 host = Setting.mail_from.to_s.gsub(%r{^.*@}, '')
274 host = "#{::Socket.gethostname}.redmine" if host.empty?
275 "<#{hash}@#{host}>"
276 end
277
278 private
279
280 def message_id(object)
281 @message_id_object = object
282 end
283
284 def references(object)
285 @references_objects ||= []
286 @references_objects << object
287 end
288 end
289
290 # Patch TMail so that message_id is not overwritten
291 module TMail
292 class Mail
293 def add_message_id( fqdn = nil )
294 self.message_id ||= ::TMail::new_message_id(fqdn)
295 end
296 end
253 end
297 end
@@ -1,68 +1,68
1 ---
1 ---
2 messages_001:
2 messages_001:
3 created_on: 2007-05-12 17:15:32 +02:00
3 created_on: 2007-05-12 17:15:32 +02:00
4 updated_on: 2007-05-12 17:15:32 +02:00
4 updated_on: 2007-05-12 17:15:32 +02:00
5 subject: First post
5 subject: First post
6 id: 1
6 id: 1
7 replies_count: 2
7 replies_count: 2
8 last_reply_id: 3
8 last_reply_id: 3
9 content: "This is the very first post\n\
9 content: "This is the very first post\n\
10 in the forum"
10 in the forum"
11 author_id: 1
11 author_id: 1
12 parent_id:
12 parent_id:
13 board_id: 1
13 board_id: 1
14 messages_002:
14 messages_002:
15 created_on: 2007-05-12 17:18:00 +02:00
15 created_on: 2007-05-12 17:18:00 +02:00
16 updated_on: 2007-05-12 17:18:00 +02:00
16 updated_on: 2007-05-12 17:18:00 +02:00
17 subject: First reply
17 subject: First reply
18 id: 2
18 id: 2
19 replies_count: 0
19 replies_count: 0
20 last_reply_id:
20 last_reply_id:
21 content: "Reply to the first post"
21 content: "Reply to the first post"
22 author_id: 1
22 author_id: 1
23 parent_id: 1
23 parent_id: 1
24 board_id: 1
24 board_id: 1
25 messages_003:
25 messages_003:
26 created_on: 2007-05-12 17:18:02 +02:00
26 created_on: 2007-05-12 17:18:02 +02:00
27 updated_on: 2007-05-12 17:18:02 +02:00
27 updated_on: 2007-05-12 17:18:02 +02:00
28 subject: "RE: First post"
28 subject: "RE: First post"
29 id: 3
29 id: 3
30 replies_count: 0
30 replies_count: 0
31 last_reply_id:
31 last_reply_id:
32 content: "An other reply"
32 content: "An other reply"
33 author_id:
33 author_id: 2
34 parent_id: 1
34 parent_id: 1
35 board_id: 1
35 board_id: 1
36 messages_004:
36 messages_004:
37 created_on: 2007-08-12 17:15:32 +02:00
37 created_on: 2007-08-12 17:15:32 +02:00
38 updated_on: 2007-08-12 17:15:32 +02:00
38 updated_on: 2007-08-12 17:15:32 +02:00
39 subject: Post 2
39 subject: Post 2
40 id: 4
40 id: 4
41 replies_count: 2
41 replies_count: 2
42 last_reply_id: 6
42 last_reply_id: 6
43 content: "This is an other post"
43 content: "This is an other post"
44 author_id:
44 author_id:
45 parent_id:
45 parent_id:
46 board_id: 1
46 board_id: 1
47 messages_005:
47 messages_005:
48 created_on: <%= 3.days.ago.to_date.to_s(:db) %>
48 created_on: <%= 3.days.ago.to_date.to_s(:db) %>
49 updated_on: <%= 3.days.ago.to_date.to_s(:db) %>
49 updated_on: <%= 3.days.ago.to_date.to_s(:db) %>
50 subject: 'RE: post 2'
50 subject: 'RE: post 2'
51 id: 5
51 id: 5
52 replies_count: 0
52 replies_count: 0
53 last_reply_id:
53 last_reply_id:
54 content: "Reply to the second post"
54 content: "Reply to the second post"
55 author_id: 1
55 author_id: 1
56 parent_id: 4
56 parent_id: 4
57 board_id: 1
57 board_id: 1
58 messages_006:
58 messages_006:
59 created_on: <%= 2.days.ago.to_date.to_s(:db) %>
59 created_on: <%= 2.days.ago.to_date.to_s(:db) %>
60 updated_on: <%= 2.days.ago.to_date.to_s(:db) %>
60 updated_on: <%= 2.days.ago.to_date.to_s(:db) %>
61 subject: 'RE: post 2'
61 subject: 'RE: post 2'
62 id: 6
62 id: 6
63 replies_count: 0
63 replies_count: 0
64 last_reply_id:
64 last_reply_id:
65 content: "Another reply to the second post"
65 content: "Another reply to the second post"
66 author_id: 3
66 author_id: 3
67 parent_id: 4
67 parent_id: 4
68 board_id: 1
68 board_id: 1
@@ -1,186 +1,224
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class MailerTest < Test::Unit::TestCase
20 class MailerTest < Test::Unit::TestCase
21 fixtures :projects, :issues, :users, :members, :documents, :attachments, :news, :tokens, :journals, :journal_details, :changesets, :trackers, :issue_statuses, :enumerations, :messages, :boards, :repositories
21 fixtures :projects, :issues, :users, :members, :documents, :attachments, :news, :tokens, :journals, :journal_details, :changesets, :trackers, :issue_statuses, :enumerations, :messages, :boards, :repositories
22
22
23 def test_generated_links_in_emails
23 def test_generated_links_in_emails
24 ActionMailer::Base.deliveries.clear
24 ActionMailer::Base.deliveries.clear
25 Setting.host_name = 'mydomain.foo'
25 Setting.host_name = 'mydomain.foo'
26 Setting.protocol = 'https'
26 Setting.protocol = 'https'
27
27
28 journal = Journal.find(2)
28 journal = Journal.find(2)
29 assert Mailer.deliver_issue_edit(journal)
29 assert Mailer.deliver_issue_edit(journal)
30
30
31 mail = ActionMailer::Base.deliveries.last
31 mail = ActionMailer::Base.deliveries.last
32 assert_kind_of TMail::Mail, mail
32 assert_kind_of TMail::Mail, mail
33 # link to the main ticket
33 # link to the main ticket
34 assert mail.body.include?('<a href="https://mydomain.foo/issues/show/1">Bug #1: Can\'t print recipes</a>')
34 assert mail.body.include?('<a href="https://mydomain.foo/issues/show/1">Bug #1: Can\'t print recipes</a>')
35
35
36 # link to a referenced ticket
36 # link to a referenced ticket
37 assert mail.body.include?('<a href="https://mydomain.foo/issues/show/2" class="issue" title="Add ingredients categories (Assigned)">#2</a>')
37 assert mail.body.include?('<a href="https://mydomain.foo/issues/show/2" class="issue" title="Add ingredients categories (Assigned)">#2</a>')
38 # link to a changeset
38 # link to a changeset
39 assert mail.body.include?('<a href="https://mydomain.foo/repositories/revision/ecookbook/2" class="changeset" title="This commit fixes #1, #2 and references #1 &amp; #3">r2</a>')
39 assert mail.body.include?('<a href="https://mydomain.foo/repositories/revision/ecookbook/2" class="changeset" title="This commit fixes #1, #2 and references #1 &amp; #3">r2</a>')
40 end
40 end
41
41
42 def test_generated_links_with_prefix
42 def test_generated_links_with_prefix
43 relative_url_root = Redmine::Utils.relative_url_root
43 relative_url_root = Redmine::Utils.relative_url_root
44 ActionMailer::Base.deliveries.clear
44 ActionMailer::Base.deliveries.clear
45 Setting.host_name = 'mydomain.foo/rdm'
45 Setting.host_name = 'mydomain.foo/rdm'
46 Setting.protocol = 'http'
46 Setting.protocol = 'http'
47 Redmine::Utils.relative_url_root = '/rdm'
47 Redmine::Utils.relative_url_root = '/rdm'
48
48
49 journal = Journal.find(2)
49 journal = Journal.find(2)
50 assert Mailer.deliver_issue_edit(journal)
50 assert Mailer.deliver_issue_edit(journal)
51
51
52 mail = ActionMailer::Base.deliveries.last
52 mail = ActionMailer::Base.deliveries.last
53 assert_kind_of TMail::Mail, mail
53 assert_kind_of TMail::Mail, mail
54 # link to the main ticket
54 # link to the main ticket
55 assert mail.body.include?('<a href="http://mydomain.foo/rdm/issues/show/1">Bug #1: Can\'t print recipes</a>')
55 assert mail.body.include?('<a href="http://mydomain.foo/rdm/issues/show/1">Bug #1: Can\'t print recipes</a>')
56
56
57 # link to a referenced ticket
57 # link to a referenced ticket
58 assert mail.body.include?('<a href="http://mydomain.foo/rdm/issues/show/2" class="issue" title="Add ingredients categories (Assigned)">#2</a>')
58 assert mail.body.include?('<a href="http://mydomain.foo/rdm/issues/show/2" class="issue" title="Add ingredients categories (Assigned)">#2</a>')
59 # link to a changeset
59 # link to a changeset
60 assert mail.body.include?('<a href="http://mydomain.foo/rdm/repositories/revision/ecookbook/2" class="changeset" title="This commit fixes #1, #2 and references #1 &amp; #3">r2</a>')
60 assert mail.body.include?('<a href="http://mydomain.foo/rdm/repositories/revision/ecookbook/2" class="changeset" title="This commit fixes #1, #2 and references #1 &amp; #3">r2</a>')
61 ensure
61 ensure
62 # restore it
62 # restore it
63 Redmine::Utils.relative_url_root = relative_url_root
63 Redmine::Utils.relative_url_root = relative_url_root
64 end
64 end
65
65
66 def test_generated_links_with_prefix_and_no_relative_url_root
66 def test_generated_links_with_prefix_and_no_relative_url_root
67 relative_url_root = Redmine::Utils.relative_url_root
67 relative_url_root = Redmine::Utils.relative_url_root
68 ActionMailer::Base.deliveries.clear
68 ActionMailer::Base.deliveries.clear
69 Setting.host_name = 'mydomain.foo/rdm'
69 Setting.host_name = 'mydomain.foo/rdm'
70 Setting.protocol = 'http'
70 Setting.protocol = 'http'
71 Redmine::Utils.relative_url_root = nil
71 Redmine::Utils.relative_url_root = nil
72
72
73 journal = Journal.find(2)
73 journal = Journal.find(2)
74 assert Mailer.deliver_issue_edit(journal)
74 assert Mailer.deliver_issue_edit(journal)
75
75
76 mail = ActionMailer::Base.deliveries.last
76 mail = ActionMailer::Base.deliveries.last
77 assert_kind_of TMail::Mail, mail
77 assert_kind_of TMail::Mail, mail
78 # link to the main ticket
78 # link to the main ticket
79 assert mail.body.include?('<a href="http://mydomain.foo/rdm/issues/show/1">Bug #1: Can\'t print recipes</a>')
79 assert mail.body.include?('<a href="http://mydomain.foo/rdm/issues/show/1">Bug #1: Can\'t print recipes</a>')
80
80
81 # link to a referenced ticket
81 # link to a referenced ticket
82 assert mail.body.include?('<a href="http://mydomain.foo/rdm/issues/show/2" class="issue" title="Add ingredients categories (Assigned)">#2</a>')
82 assert mail.body.include?('<a href="http://mydomain.foo/rdm/issues/show/2" class="issue" title="Add ingredients categories (Assigned)">#2</a>')
83 # link to a changeset
83 # link to a changeset
84 assert mail.body.include?('<a href="http://mydomain.foo/rdm/repositories/revision/ecookbook/2" class="changeset" title="This commit fixes #1, #2 and references #1 &amp; #3">r2</a>')
84 assert mail.body.include?('<a href="http://mydomain.foo/rdm/repositories/revision/ecookbook/2" class="changeset" title="This commit fixes #1, #2 and references #1 &amp; #3">r2</a>')
85 ensure
85 ensure
86 # restore it
86 # restore it
87 Redmine::Utils.relative_url_root = relative_url_root
87 Redmine::Utils.relative_url_root = relative_url_root
88 end
88 end
89
89
90 def test_plain_text_mail
90 def test_plain_text_mail
91 Setting.plain_text_mail = 1
91 Setting.plain_text_mail = 1
92 journal = Journal.find(2)
92 journal = Journal.find(2)
93 Mailer.deliver_issue_edit(journal)
93 Mailer.deliver_issue_edit(journal)
94 mail = ActionMailer::Base.deliveries.last
94 mail = ActionMailer::Base.deliveries.last
95 assert !mail.body.include?('<a href="https://mydomain.foo/issues/show/1">Bug #1: Can\'t print recipes</a>')
95 assert !mail.body.include?('<a href="https://mydomain.foo/issues/show/1">Bug #1: Can\'t print recipes</a>')
96 end
96 end
97
97
98
98 def test_issue_add_message_id
99 ActionMailer::Base.deliveries.clear
100 issue = Issue.find(1)
101 Mailer.deliver_issue_add(issue)
102 mail = ActionMailer::Base.deliveries.last
103 assert_not_nil mail
104 assert_equal Mailer.message_id_for(issue), mail.message_id
105 assert_nil mail.references
106 end
107
108 def test_issue_edit_message_id
109 ActionMailer::Base.deliveries.clear
110 journal = Journal.find(1)
111 Mailer.deliver_issue_edit(journal)
112 mail = ActionMailer::Base.deliveries.last
113 assert_not_nil mail
114 assert_equal Mailer.message_id_for(journal), mail.message_id
115 assert_equal Mailer.message_id_for(journal.issue), mail.references.to_s
116 end
117
118 def test_message_posted_message_id
119 ActionMailer::Base.deliveries.clear
120 message = Message.find(1)
121 Mailer.deliver_message_posted(message, message.author.mail)
122 mail = ActionMailer::Base.deliveries.last
123 assert_not_nil mail
124 assert_equal Mailer.message_id_for(message), mail.message_id
125 assert_nil mail.references
126 end
127
128 def test_reply_posted_message_id
129 ActionMailer::Base.deliveries.clear
130 message = Message.find(3)
131 Mailer.deliver_message_posted(message, message.author.mail)
132 mail = ActionMailer::Base.deliveries.last
133 assert_not_nil mail
134 assert_equal Mailer.message_id_for(message), mail.message_id
135 assert_equal Mailer.message_id_for(message.parent), mail.references.to_s
136 end
99
137
100 # test mailer methods for each language
138 # test mailer methods for each language
101 def test_issue_add
139 def test_issue_add
102 issue = Issue.find(1)
140 issue = Issue.find(1)
103 GLoc.valid_languages.each do |lang|
141 GLoc.valid_languages.each do |lang|
104 Setting.default_language = lang.to_s
142 Setting.default_language = lang.to_s
105 assert Mailer.deliver_issue_add(issue)
143 assert Mailer.deliver_issue_add(issue)
106 end
144 end
107 end
145 end
108
146
109 def test_issue_edit
147 def test_issue_edit
110 journal = Journal.find(1)
148 journal = Journal.find(1)
111 GLoc.valid_languages.each do |lang|
149 GLoc.valid_languages.each do |lang|
112 Setting.default_language = lang.to_s
150 Setting.default_language = lang.to_s
113 assert Mailer.deliver_issue_edit(journal)
151 assert Mailer.deliver_issue_edit(journal)
114 end
152 end
115 end
153 end
116
154
117 def test_document_added
155 def test_document_added
118 document = Document.find(1)
156 document = Document.find(1)
119 GLoc.valid_languages.each do |lang|
157 GLoc.valid_languages.each do |lang|
120 Setting.default_language = lang.to_s
158 Setting.default_language = lang.to_s
121 assert Mailer.deliver_document_added(document)
159 assert Mailer.deliver_document_added(document)
122 end
160 end
123 end
161 end
124
162
125 def test_attachments_added
163 def test_attachments_added
126 attachements = [ Attachment.find_by_container_type('Document') ]
164 attachements = [ Attachment.find_by_container_type('Document') ]
127 GLoc.valid_languages.each do |lang|
165 GLoc.valid_languages.each do |lang|
128 Setting.default_language = lang.to_s
166 Setting.default_language = lang.to_s
129 assert Mailer.deliver_attachments_added(attachements)
167 assert Mailer.deliver_attachments_added(attachements)
130 end
168 end
131 end
169 end
132
170
133 def test_news_added
171 def test_news_added
134 news = News.find(:first)
172 news = News.find(:first)
135 GLoc.valid_languages.each do |lang|
173 GLoc.valid_languages.each do |lang|
136 Setting.default_language = lang.to_s
174 Setting.default_language = lang.to_s
137 assert Mailer.deliver_news_added(news)
175 assert Mailer.deliver_news_added(news)
138 end
176 end
139 end
177 end
140
178
141 def test_message_posted
179 def test_message_posted
142 message = Message.find(:first)
180 message = Message.find(:first)
143 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
181 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
144 recipients = recipients.compact.uniq
182 recipients = recipients.compact.uniq
145 GLoc.valid_languages.each do |lang|
183 GLoc.valid_languages.each do |lang|
146 Setting.default_language = lang.to_s
184 Setting.default_language = lang.to_s
147 assert Mailer.deliver_message_posted(message, recipients)
185 assert Mailer.deliver_message_posted(message, recipients)
148 end
186 end
149 end
187 end
150
188
151 def test_account_information
189 def test_account_information
152 user = User.find(:first)
190 user = User.find(:first)
153 GLoc.valid_languages.each do |lang|
191 GLoc.valid_languages.each do |lang|
154 user.update_attribute :language, lang.to_s
192 user.update_attribute :language, lang.to_s
155 user.reload
193 user.reload
156 assert Mailer.deliver_account_information(user, 'pAsswORd')
194 assert Mailer.deliver_account_information(user, 'pAsswORd')
157 end
195 end
158 end
196 end
159
197
160 def test_lost_password
198 def test_lost_password
161 token = Token.find(2)
199 token = Token.find(2)
162 GLoc.valid_languages.each do |lang|
200 GLoc.valid_languages.each do |lang|
163 token.user.update_attribute :language, lang.to_s
201 token.user.update_attribute :language, lang.to_s
164 token.reload
202 token.reload
165 assert Mailer.deliver_lost_password(token)
203 assert Mailer.deliver_lost_password(token)
166 end
204 end
167 end
205 end
168
206
169 def test_register
207 def test_register
170 token = Token.find(1)
208 token = Token.find(1)
171 GLoc.valid_languages.each do |lang|
209 GLoc.valid_languages.each do |lang|
172 token.user.update_attribute :language, lang.to_s
210 token.user.update_attribute :language, lang.to_s
173 token.reload
211 token.reload
174 assert Mailer.deliver_register(token)
212 assert Mailer.deliver_register(token)
175 end
213 end
176 end
214 end
177
215
178 def test_reminders
216 def test_reminders
179 ActionMailer::Base.deliveries.clear
217 ActionMailer::Base.deliveries.clear
180 Mailer.reminders(:days => 42)
218 Mailer.reminders(:days => 42)
181 assert_equal 1, ActionMailer::Base.deliveries.size
219 assert_equal 1, ActionMailer::Base.deliveries.size
182 mail = ActionMailer::Base.deliveries.last
220 mail = ActionMailer::Base.deliveries.last
183 assert mail.bcc.include?('dlopper@somenet.foo')
221 assert mail.bcc.include?('dlopper@somenet.foo')
184 assert mail.body.include?('Bug #3: Error 281 when updating a recipe')
222 assert mail.body.include?('Bug #3: Error 281 when updating a recipe')
185 end
223 end
186 end
224 end
General Comments 0
You need to be logged in to leave comments. Login now