##// END OF EJS Templates
Adds a X-Redmine-Sender header to email notifications (#5643)....
Jean-Philippe Lang -
r8665:967f42aa98f5
parent child
Show More
@@ -1,472 +1,477
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 layout 'mailer'
19 layout 'mailer'
20 helper :application
20 helper :application
21 helper :issues
21 helper :issues
22 helper :custom_fields
22 helper :custom_fields
23
23
24 include ActionController::UrlWriter
24 include ActionController::UrlWriter
25 include Redmine::I18n
25 include Redmine::I18n
26
26
27 def self.default_url_options
27 def self.default_url_options
28 h = Setting.host_name
28 h = Setting.host_name
29 h = h.to_s.gsub(%r{\/.*$}, '') unless Redmine::Utils.relative_url_root.blank?
29 h = h.to_s.gsub(%r{\/.*$}, '') unless Redmine::Utils.relative_url_root.blank?
30 { :host => h, :protocol => Setting.protocol }
30 { :host => h, :protocol => Setting.protocol }
31 end
31 end
32
32
33 # Builds a tmail object used to email recipients of the added issue.
33 # Builds a tmail object used to email recipients of the added issue.
34 #
34 #
35 # Example:
35 # Example:
36 # issue_add(issue) => tmail object
36 # issue_add(issue) => tmail object
37 # Mailer.deliver_issue_add(issue) => sends an email to issue recipients
37 # Mailer.deliver_issue_add(issue) => sends an email to issue recipients
38 def issue_add(issue)
38 def issue_add(issue)
39 redmine_headers 'Project' => issue.project.identifier,
39 redmine_headers 'Project' => issue.project.identifier,
40 'Issue-Id' => issue.id,
40 'Issue-Id' => issue.id,
41 'Issue-Author' => issue.author.login
41 'Issue-Author' => issue.author.login
42 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
42 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
43 message_id issue
43 message_id issue
44 @author = issue.author
44 recipients issue.recipients
45 recipients issue.recipients
45 cc(issue.watcher_recipients - @recipients)
46 cc(issue.watcher_recipients - @recipients)
46 subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
47 subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
47 body :issue => issue,
48 body :issue => issue,
48 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
49 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
49 render_multipart('issue_add', body)
50 render_multipart('issue_add', body)
50 end
51 end
51
52
52 # Builds a tmail object used to email recipients of the edited issue.
53 # Builds a tmail object used to email recipients of the edited issue.
53 #
54 #
54 # Example:
55 # Example:
55 # issue_edit(journal) => tmail object
56 # issue_edit(journal) => tmail object
56 # Mailer.deliver_issue_edit(journal) => sends an email to issue recipients
57 # Mailer.deliver_issue_edit(journal) => sends an email to issue recipients
57 def issue_edit(journal)
58 def issue_edit(journal)
58 issue = journal.journalized.reload
59 issue = journal.journalized.reload
59 redmine_headers 'Project' => issue.project.identifier,
60 redmine_headers 'Project' => issue.project.identifier,
60 'Issue-Id' => issue.id,
61 'Issue-Id' => issue.id,
61 'Issue-Author' => issue.author.login
62 'Issue-Author' => issue.author.login
62 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
63 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
63 message_id journal
64 message_id journal
64 references issue
65 references issue
65 @author = journal.user
66 @author = journal.user
66 recipients issue.recipients
67 recipients issue.recipients
67 # Watchers in cc
68 # Watchers in cc
68 cc(issue.watcher_recipients - @recipients)
69 cc(issue.watcher_recipients - @recipients)
69 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
70 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
70 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
71 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
71 s << issue.subject
72 s << issue.subject
72 subject s
73 subject s
73 body :issue => issue,
74 body :issue => issue,
74 :journal => journal,
75 :journal => journal,
75 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}")
76 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}")
76
77
77 render_multipart('issue_edit', body)
78 render_multipart('issue_edit', body)
78 end
79 end
79
80
80 def reminder(user, issues, days)
81 def reminder(user, issues, days)
81 set_language_if_valid user.language
82 set_language_if_valid user.language
82 recipients user.mail
83 recipients user.mail
83 subject l(:mail_subject_reminder, :count => issues.size, :days => days)
84 subject l(:mail_subject_reminder, :count => issues.size, :days => days)
84 body :issues => issues,
85 body :issues => issues,
85 :days => days,
86 :days => days,
86 :issues_url => url_for(:controller => 'issues', :action => 'index',
87 :issues_url => url_for(:controller => 'issues', :action => 'index',
87 :set_filter => 1, :assigned_to_id => user.id,
88 :set_filter => 1, :assigned_to_id => user.id,
88 :sort => 'due_date:asc')
89 :sort => 'due_date:asc')
89 render_multipart('reminder', body)
90 render_multipart('reminder', body)
90 end
91 end
91
92
92 # Builds a tmail object used to email users belonging to the added document's project.
93 # Builds a tmail object used to email users belonging to the added document's project.
93 #
94 #
94 # Example:
95 # Example:
95 # document_added(document) => tmail object
96 # document_added(document) => tmail object
96 # Mailer.deliver_document_added(document) => sends an email to the document's project recipients
97 # Mailer.deliver_document_added(document) => sends an email to the document's project recipients
97 def document_added(document)
98 def document_added(document)
98 redmine_headers 'Project' => document.project.identifier
99 redmine_headers 'Project' => document.project.identifier
99 recipients document.recipients
100 recipients document.recipients
100 subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
101 subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
101 body :document => document,
102 body :document => document,
102 :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
103 :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
103 render_multipart('document_added', body)
104 render_multipart('document_added', body)
104 end
105 end
105
106
106 # Builds a tmail object used to email recipients of a project when an attachements are added.
107 # Builds a tmail object used to email recipients of a project when an attachements are added.
107 #
108 #
108 # Example:
109 # Example:
109 # attachments_added(attachments) => tmail object
110 # attachments_added(attachments) => tmail object
110 # Mailer.deliver_attachments_added(attachments) => sends an email to the project's recipients
111 # Mailer.deliver_attachments_added(attachments) => sends an email to the project's recipients
111 def attachments_added(attachments)
112 def attachments_added(attachments)
112 container = attachments.first.container
113 container = attachments.first.container
113 added_to = ''
114 added_to = ''
114 added_to_url = ''
115 added_to_url = ''
115 case container.class.name
116 case container.class.name
116 when 'Project'
117 when 'Project'
117 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container)
118 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container)
118 added_to = "#{l(:label_project)}: #{container}"
119 added_to = "#{l(:label_project)}: #{container}"
119 recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
120 recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
120 when 'Version'
121 when 'Version'
121 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project)
122 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project)
122 added_to = "#{l(:label_version)}: #{container.name}"
123 added_to = "#{l(:label_version)}: #{container.name}"
123 recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
124 recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
124 when 'Document'
125 when 'Document'
125 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
126 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
126 added_to = "#{l(:label_document)}: #{container.title}"
127 added_to = "#{l(:label_document)}: #{container.title}"
127 recipients container.recipients
128 recipients container.recipients
128 end
129 end
129 redmine_headers 'Project' => container.project.identifier
130 redmine_headers 'Project' => container.project.identifier
130 subject "[#{container.project.name}] #{l(:label_attachment_new)}"
131 subject "[#{container.project.name}] #{l(:label_attachment_new)}"
131 body :attachments => attachments,
132 body :attachments => attachments,
132 :added_to => added_to,
133 :added_to => added_to,
133 :added_to_url => added_to_url
134 :added_to_url => added_to_url
134 render_multipart('attachments_added', body)
135 render_multipart('attachments_added', body)
135 end
136 end
136
137
137 # Builds a tmail object used to email recipients of a news' project when a news item is added.
138 # Builds a tmail object used to email recipients of a news' project when a news item is added.
138 #
139 #
139 # Example:
140 # Example:
140 # news_added(news) => tmail object
141 # news_added(news) => tmail object
141 # Mailer.deliver_news_added(news) => sends an email to the news' project recipients
142 # Mailer.deliver_news_added(news) => sends an email to the news' project recipients
142 def news_added(news)
143 def news_added(news)
143 redmine_headers 'Project' => news.project.identifier
144 redmine_headers 'Project' => news.project.identifier
144 message_id news
145 message_id news
145 recipients news.recipients
146 recipients news.recipients
146 subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
147 subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
147 body :news => news,
148 body :news => news,
148 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
149 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
149 render_multipart('news_added', body)
150 render_multipart('news_added', body)
150 end
151 end
151
152
152 # Builds a tmail object used to email recipients of a news' project when a news comment is added.
153 # Builds a tmail object used to email recipients of a news' project when a news comment is added.
153 #
154 #
154 # Example:
155 # Example:
155 # news_comment_added(comment) => tmail object
156 # news_comment_added(comment) => tmail object
156 # Mailer.news_comment_added(comment) => sends an email to the news' project recipients
157 # Mailer.news_comment_added(comment) => sends an email to the news' project recipients
157 def news_comment_added(comment)
158 def news_comment_added(comment)
158 news = comment.commented
159 news = comment.commented
159 redmine_headers 'Project' => news.project.identifier
160 redmine_headers 'Project' => news.project.identifier
160 message_id comment
161 message_id comment
161 recipients news.recipients
162 recipients news.recipients
162 cc news.watcher_recipients
163 cc news.watcher_recipients
163 subject "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}"
164 subject "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}"
164 body :news => news,
165 body :news => news,
165 :comment => comment,
166 :comment => comment,
166 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
167 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
167 render_multipart('news_comment_added', body)
168 render_multipart('news_comment_added', body)
168 end
169 end
169
170
170 # Builds a tmail object used to email the recipients of the specified message that was posted.
171 # Builds a tmail object used to email the recipients of the specified message that was posted.
171 #
172 #
172 # Example:
173 # Example:
173 # message_posted(message) => tmail object
174 # message_posted(message) => tmail object
174 # Mailer.deliver_message_posted(message) => sends an email to the recipients
175 # Mailer.deliver_message_posted(message) => sends an email to the recipients
175 def message_posted(message)
176 def message_posted(message)
176 redmine_headers 'Project' => message.project.identifier,
177 redmine_headers 'Project' => message.project.identifier,
177 'Topic-Id' => (message.parent_id || message.id)
178 'Topic-Id' => (message.parent_id || message.id)
178 message_id message
179 message_id message
179 references message.parent unless message.parent.nil?
180 references message.parent unless message.parent.nil?
180 recipients(message.recipients)
181 recipients(message.recipients)
181 cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients)
182 cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients)
182 subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
183 subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
183 body :message => message,
184 body :message => message,
184 :message_url => url_for(message.event_url)
185 :message_url => url_for(message.event_url)
185 render_multipart('message_posted', body)
186 render_multipart('message_posted', body)
186 end
187 end
187
188
188 # Builds a tmail object used to email the recipients of a project of the specified wiki content was added.
189 # Builds a tmail object used to email the recipients of a project of the specified wiki content was added.
189 #
190 #
190 # Example:
191 # Example:
191 # wiki_content_added(wiki_content) => tmail object
192 # wiki_content_added(wiki_content) => tmail object
192 # Mailer.deliver_wiki_content_added(wiki_content) => sends an email to the project's recipients
193 # Mailer.deliver_wiki_content_added(wiki_content) => sends an email to the project's recipients
193 def wiki_content_added(wiki_content)
194 def wiki_content_added(wiki_content)
194 redmine_headers 'Project' => wiki_content.project.identifier,
195 redmine_headers 'Project' => wiki_content.project.identifier,
195 'Wiki-Page-Id' => wiki_content.page.id
196 'Wiki-Page-Id' => wiki_content.page.id
196 message_id wiki_content
197 message_id wiki_content
197 recipients wiki_content.recipients
198 recipients wiki_content.recipients
198 cc(wiki_content.page.wiki.watcher_recipients - recipients)
199 cc(wiki_content.page.wiki.watcher_recipients - recipients)
199 subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :id => wiki_content.page.pretty_title)}"
200 subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :id => wiki_content.page.pretty_title)}"
200 body :wiki_content => wiki_content,
201 body :wiki_content => wiki_content,
201 :wiki_content_url => url_for(:controller => 'wiki', :action => 'show',
202 :wiki_content_url => url_for(:controller => 'wiki', :action => 'show',
202 :project_id => wiki_content.project,
203 :project_id => wiki_content.project,
203 :id => wiki_content.page.title)
204 :id => wiki_content.page.title)
204 render_multipart('wiki_content_added', body)
205 render_multipart('wiki_content_added', body)
205 end
206 end
206
207
207 # Builds a tmail object used to email the recipients of a project of the specified wiki content was updated.
208 # Builds a tmail object used to email the recipients of a project of the specified wiki content was updated.
208 #
209 #
209 # Example:
210 # Example:
210 # wiki_content_updated(wiki_content) => tmail object
211 # wiki_content_updated(wiki_content) => tmail object
211 # Mailer.deliver_wiki_content_updated(wiki_content) => sends an email to the project's recipients
212 # Mailer.deliver_wiki_content_updated(wiki_content) => sends an email to the project's recipients
212 def wiki_content_updated(wiki_content)
213 def wiki_content_updated(wiki_content)
213 redmine_headers 'Project' => wiki_content.project.identifier,
214 redmine_headers 'Project' => wiki_content.project.identifier,
214 'Wiki-Page-Id' => wiki_content.page.id
215 'Wiki-Page-Id' => wiki_content.page.id
215 message_id wiki_content
216 message_id wiki_content
216 recipients wiki_content.recipients
217 recipients wiki_content.recipients
217 cc(wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients)
218 cc(wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients)
218 subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :id => wiki_content.page.pretty_title)}"
219 subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :id => wiki_content.page.pretty_title)}"
219 body :wiki_content => wiki_content,
220 body :wiki_content => wiki_content,
220 :wiki_content_url => url_for(:controller => 'wiki', :action => 'show',
221 :wiki_content_url => url_for(:controller => 'wiki', :action => 'show',
221 :project_id => wiki_content.project,
222 :project_id => wiki_content.project,
222 :id => wiki_content.page.title),
223 :id => wiki_content.page.title),
223 :wiki_diff_url => url_for(:controller => 'wiki', :action => 'diff',
224 :wiki_diff_url => url_for(:controller => 'wiki', :action => 'diff',
224 :project_id => wiki_content.project, :id => wiki_content.page.title,
225 :project_id => wiki_content.project, :id => wiki_content.page.title,
225 :version => wiki_content.version)
226 :version => wiki_content.version)
226 render_multipart('wiki_content_updated', body)
227 render_multipart('wiki_content_updated', body)
227 end
228 end
228
229
229 # Builds a tmail object used to email the specified user their account information.
230 # Builds a tmail object used to email the specified user their account information.
230 #
231 #
231 # Example:
232 # Example:
232 # account_information(user, password) => tmail object
233 # account_information(user, password) => tmail object
233 # Mailer.deliver_account_information(user, password) => sends account information to the user
234 # Mailer.deliver_account_information(user, password) => sends account information to the user
234 def account_information(user, password)
235 def account_information(user, password)
235 set_language_if_valid user.language
236 set_language_if_valid user.language
236 recipients user.mail
237 recipients user.mail
237 subject l(:mail_subject_register, Setting.app_title)
238 subject l(:mail_subject_register, Setting.app_title)
238 body :user => user,
239 body :user => user,
239 :password => password,
240 :password => password,
240 :login_url => url_for(:controller => 'account', :action => 'login')
241 :login_url => url_for(:controller => 'account', :action => 'login')
241 render_multipart('account_information', body)
242 render_multipart('account_information', body)
242 end
243 end
243
244
244 # Builds a tmail object used to email all active administrators of an account activation request.
245 # Builds a tmail object used to email all active administrators of an account activation request.
245 #
246 #
246 # Example:
247 # Example:
247 # account_activation_request(user) => tmail object
248 # account_activation_request(user) => tmail object
248 # Mailer.deliver_account_activation_request(user)=> sends an email to all active administrators
249 # Mailer.deliver_account_activation_request(user)=> sends an email to all active administrators
249 def account_activation_request(user)
250 def account_activation_request(user)
250 # Send the email to all active administrators
251 # Send the email to all active administrators
251 recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
252 recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
252 subject l(:mail_subject_account_activation_request, Setting.app_title)
253 subject l(:mail_subject_account_activation_request, Setting.app_title)
253 body :user => user,
254 body :user => user,
254 :url => url_for(:controller => 'users', :action => 'index',
255 :url => url_for(:controller => 'users', :action => 'index',
255 :status => User::STATUS_REGISTERED,
256 :status => User::STATUS_REGISTERED,
256 :sort_key => 'created_on', :sort_order => 'desc')
257 :sort_key => 'created_on', :sort_order => 'desc')
257 render_multipart('account_activation_request', body)
258 render_multipart('account_activation_request', body)
258 end
259 end
259
260
260 # Builds a tmail object used to email the specified user that their account was activated by an administrator.
261 # Builds a tmail object used to email the specified user that their account was activated by an administrator.
261 #
262 #
262 # Example:
263 # Example:
263 # account_activated(user) => tmail object
264 # account_activated(user) => tmail object
264 # Mailer.deliver_account_activated(user) => sends an email to the registered user
265 # Mailer.deliver_account_activated(user) => sends an email to the registered user
265 def account_activated(user)
266 def account_activated(user)
266 set_language_if_valid user.language
267 set_language_if_valid user.language
267 recipients user.mail
268 recipients user.mail
268 subject l(:mail_subject_register, Setting.app_title)
269 subject l(:mail_subject_register, Setting.app_title)
269 body :user => user,
270 body :user => user,
270 :login_url => url_for(:controller => 'account', :action => 'login')
271 :login_url => url_for(:controller => 'account', :action => 'login')
271 render_multipart('account_activated', body)
272 render_multipart('account_activated', body)
272 end
273 end
273
274
274 def lost_password(token)
275 def lost_password(token)
275 set_language_if_valid(token.user.language)
276 set_language_if_valid(token.user.language)
276 recipients token.user.mail
277 recipients token.user.mail
277 subject l(:mail_subject_lost_password, Setting.app_title)
278 subject l(:mail_subject_lost_password, Setting.app_title)
278 body :token => token,
279 body :token => token,
279 :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
280 :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
280 render_multipart('lost_password', body)
281 render_multipart('lost_password', body)
281 end
282 end
282
283
283 def register(token)
284 def register(token)
284 set_language_if_valid(token.user.language)
285 set_language_if_valid(token.user.language)
285 recipients token.user.mail
286 recipients token.user.mail
286 subject l(:mail_subject_register, Setting.app_title)
287 subject l(:mail_subject_register, Setting.app_title)
287 body :token => token,
288 body :token => token,
288 :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
289 :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
289 render_multipart('register', body)
290 render_multipart('register', body)
290 end
291 end
291
292
292 def test(user)
293 def test(user)
293 set_language_if_valid(user.language)
294 set_language_if_valid(user.language)
294 recipients user.mail
295 recipients user.mail
295 subject 'Redmine test'
296 subject 'Redmine test'
296 body :url => url_for(:controller => 'welcome')
297 body :url => url_for(:controller => 'welcome')
297 render_multipart('test', body)
298 render_multipart('test', body)
298 end
299 end
299
300
300 # Overrides default deliver! method to prevent from sending an email
301 # Overrides default deliver! method to prevent from sending an email
301 # with no recipient, cc or bcc
302 # with no recipient, cc or bcc
302 def deliver!(mail = @mail)
303 def deliver!(mail = @mail)
303 set_language_if_valid @initial_language
304 set_language_if_valid @initial_language
304 return false if (recipients.nil? || recipients.empty?) &&
305 return false if (recipients.nil? || recipients.empty?) &&
305 (cc.nil? || cc.empty?) &&
306 (cc.nil? || cc.empty?) &&
306 (bcc.nil? || bcc.empty?)
307 (bcc.nil? || bcc.empty?)
307
308
308 # Set Message-Id and References
309 # Set Message-Id and References
309 if @message_id_object
310 if @message_id_object
310 mail.message_id = self.class.message_id_for(@message_id_object)
311 mail.message_id = self.class.message_id_for(@message_id_object)
311 end
312 end
312 if @references_objects
313 if @references_objects
313 mail.references = @references_objects.collect {|o| self.class.message_id_for(o)}
314 mail.references = @references_objects.collect {|o| self.class.message_id_for(o)}
314 end
315 end
315
316
316 # Log errors when raise_delivery_errors is set to false, Rails does not
317 # Log errors when raise_delivery_errors is set to false, Rails does not
317 raise_errors = self.class.raise_delivery_errors
318 raise_errors = self.class.raise_delivery_errors
318 self.class.raise_delivery_errors = true
319 self.class.raise_delivery_errors = true
319 begin
320 begin
320 return super(mail)
321 return super(mail)
321 rescue Exception => e
322 rescue Exception => e
322 if raise_errors
323 if raise_errors
323 raise e
324 raise e
324 elsif mylogger
325 elsif mylogger
325 mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/configuration.yml."
326 mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/configuration.yml."
326 end
327 end
327 ensure
328 ensure
328 self.class.raise_delivery_errors = raise_errors
329 self.class.raise_delivery_errors = raise_errors
329 end
330 end
330 end
331 end
331
332
332 # Sends reminders to issue assignees
333 # Sends reminders to issue assignees
333 # Available options:
334 # Available options:
334 # * :days => how many days in the future to remind about (defaults to 7)
335 # * :days => how many days in the future to remind about (defaults to 7)
335 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
336 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
336 # * :project => id or identifier of project to process (defaults to all projects)
337 # * :project => id or identifier of project to process (defaults to all projects)
337 # * :users => array of user ids who should be reminded
338 # * :users => array of user ids who should be reminded
338 def self.reminders(options={})
339 def self.reminders(options={})
339 days = options[:days] || 7
340 days = options[:days] || 7
340 project = options[:project] ? Project.find(options[:project]) : nil
341 project = options[:project] ? Project.find(options[:project]) : nil
341 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
342 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
342 user_ids = options[:users]
343 user_ids = options[:users]
343
344
344 scope = Issue.open.scoped(:conditions => ["#{Issue.table_name}.assigned_to_id IS NOT NULL" +
345 scope = Issue.open.scoped(:conditions => ["#{Issue.table_name}.assigned_to_id IS NOT NULL" +
345 " AND #{Project.table_name}.status = #{Project::STATUS_ACTIVE}" +
346 " AND #{Project.table_name}.status = #{Project::STATUS_ACTIVE}" +
346 " AND #{Issue.table_name}.due_date <= ?", days.day.from_now.to_date]
347 " AND #{Issue.table_name}.due_date <= ?", days.day.from_now.to_date]
347 )
348 )
348 scope = scope.scoped(:conditions => {:assigned_to_id => user_ids}) if user_ids.present?
349 scope = scope.scoped(:conditions => {:assigned_to_id => user_ids}) if user_ids.present?
349 scope = scope.scoped(:conditions => {:project_id => project.id}) if project
350 scope = scope.scoped(:conditions => {:project_id => project.id}) if project
350 scope = scope.scoped(:conditions => {:tracker_id => tracker.id}) if tracker
351 scope = scope.scoped(:conditions => {:tracker_id => tracker.id}) if tracker
351
352
352 issues_by_assignee = scope.all(:include => [:status, :assigned_to, :project, :tracker]).group_by(&:assigned_to)
353 issues_by_assignee = scope.all(:include => [:status, :assigned_to, :project, :tracker]).group_by(&:assigned_to)
353 issues_by_assignee.each do |assignee, issues|
354 issues_by_assignee.each do |assignee, issues|
354 deliver_reminder(assignee, issues, days) if assignee && assignee.active?
355 deliver_reminder(assignee, issues, days) if assignee && assignee.active?
355 end
356 end
356 end
357 end
357
358
358 # Activates/desactivates email deliveries during +block+
359 # Activates/desactivates email deliveries during +block+
359 def self.with_deliveries(enabled = true, &block)
360 def self.with_deliveries(enabled = true, &block)
360 was_enabled = ActionMailer::Base.perform_deliveries
361 was_enabled = ActionMailer::Base.perform_deliveries
361 ActionMailer::Base.perform_deliveries = !!enabled
362 ActionMailer::Base.perform_deliveries = !!enabled
362 yield
363 yield
363 ensure
364 ensure
364 ActionMailer::Base.perform_deliveries = was_enabled
365 ActionMailer::Base.perform_deliveries = was_enabled
365 end
366 end
366
367
367 private
368 private
368 def initialize_defaults(method_name)
369 def initialize_defaults(method_name)
369 super
370 super
370 @initial_language = current_language
371 @initial_language = current_language
371 set_language_if_valid Setting.default_language
372 set_language_if_valid Setting.default_language
372 from Setting.mail_from
373 from Setting.mail_from
373
374
374 # Common headers
375 # Common headers
375 headers 'X-Mailer' => 'Redmine',
376 headers 'X-Mailer' => 'Redmine',
376 'X-Redmine-Host' => Setting.host_name,
377 'X-Redmine-Host' => Setting.host_name,
377 'X-Redmine-Site' => Setting.app_title,
378 'X-Redmine-Site' => Setting.app_title,
378 'X-Auto-Response-Suppress' => 'OOF',
379 'X-Auto-Response-Suppress' => 'OOF',
379 'Auto-Submitted' => 'auto-generated'
380 'Auto-Submitted' => 'auto-generated'
380 end
381 end
381
382
382 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
383 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
383 def redmine_headers(h)
384 def redmine_headers(h)
384 h.each { |k,v| headers["X-Redmine-#{k}"] = v.to_s }
385 h.each { |k,v| headers["X-Redmine-#{k}"] = v.to_s }
385 end
386 end
386
387
387 # Overrides the create_mail method
388 # Overrides the create_mail method
388 def create_mail
389 def create_mail
389 # Removes the current user from the recipients and cc
390 # Removes the current user from the recipients and cc
390 # if he doesn't want to receive notifications about what he does
391 # if he doesn't want to receive notifications about what he does
391 @author ||= User.current
392 @author ||= User.current
392 if @author.pref[:no_self_notified]
393 if @author.pref[:no_self_notified]
393 recipients.delete(@author.mail) if recipients
394 recipients.delete(@author.mail) if recipients
394 cc.delete(@author.mail) if cc
395 cc.delete(@author.mail) if cc
395 end
396 end
396
397
398 if @author.logged?
399 redmine_headers 'Sender' => @author.login
400 end
401
397 notified_users = [recipients, cc].flatten.compact.uniq
402 notified_users = [recipients, cc].flatten.compact.uniq
398 # Rails would log recipients only, not cc and bcc
403 # Rails would log recipients only, not cc and bcc
399 mylogger.info "Sending email notification to: #{notified_users.join(', ')}" if mylogger
404 mylogger.info "Sending email notification to: #{notified_users.join(', ')}" if mylogger
400
405
401 # Blind carbon copy recipients
406 # Blind carbon copy recipients
402 if Setting.bcc_recipients?
407 if Setting.bcc_recipients?
403 bcc(notified_users)
408 bcc(notified_users)
404 recipients []
409 recipients []
405 cc []
410 cc []
406 end
411 end
407 super
412 super
408 end
413 end
409
414
410 # Rails 2.3 has problems rendering implicit multipart messages with
415 # Rails 2.3 has problems rendering implicit multipart messages with
411 # layouts so this method will wrap an multipart messages with
416 # layouts so this method will wrap an multipart messages with
412 # explicit parts.
417 # explicit parts.
413 #
418 #
414 # https://rails.lighthouseapp.com/projects/8994/tickets/2338-actionmailer-mailer-views-and-content-type
419 # https://rails.lighthouseapp.com/projects/8994/tickets/2338-actionmailer-mailer-views-and-content-type
415 # https://rails.lighthouseapp.com/projects/8994/tickets/1799-actionmailer-doesnt-set-template_format-when-rendering-layouts
420 # https://rails.lighthouseapp.com/projects/8994/tickets/1799-actionmailer-doesnt-set-template_format-when-rendering-layouts
416
421
417 def render_multipart(method_name, body)
422 def render_multipart(method_name, body)
418 if Setting.plain_text_mail?
423 if Setting.plain_text_mail?
419 content_type "text/plain"
424 content_type "text/plain"
420 body render(:file => "#{method_name}.text.erb",
425 body render(:file => "#{method_name}.text.erb",
421 :body => body,
426 :body => body,
422 :layout => 'mailer.text.erb')
427 :layout => 'mailer.text.erb')
423 else
428 else
424 content_type "multipart/alternative"
429 content_type "multipart/alternative"
425 part :content_type => "text/plain",
430 part :content_type => "text/plain",
426 :body => render(:file => "#{method_name}.text.erb",
431 :body => render(:file => "#{method_name}.text.erb",
427 :body => body, :layout => 'mailer.text.erb')
432 :body => body, :layout => 'mailer.text.erb')
428 part :content_type => "text/html",
433 part :content_type => "text/html",
429 :body => render_message("#{method_name}.html.erb", body)
434 :body => render_message("#{method_name}.html.erb", body)
430 end
435 end
431 end
436 end
432
437
433 # Makes partial rendering work with Rails 1.2 (retro-compatibility)
438 # Makes partial rendering work with Rails 1.2 (retro-compatibility)
434 def self.controller_path
439 def self.controller_path
435 ''
440 ''
436 end unless respond_to?('controller_path')
441 end unless respond_to?('controller_path')
437
442
438 # Returns a predictable Message-Id for the given object
443 # Returns a predictable Message-Id for the given object
439 def self.message_id_for(object)
444 def self.message_id_for(object)
440 # id + timestamp should reduce the odds of a collision
445 # id + timestamp should reduce the odds of a collision
441 # as far as we don't send multiple emails for the same object
446 # as far as we don't send multiple emails for the same object
442 timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on)
447 timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on)
443 hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{timestamp.strftime("%Y%m%d%H%M%S")}"
448 hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{timestamp.strftime("%Y%m%d%H%M%S")}"
444 host = Setting.mail_from.to_s.gsub(%r{^.*@}, '')
449 host = Setting.mail_from.to_s.gsub(%r{^.*@}, '')
445 host = "#{::Socket.gethostname}.redmine" if host.empty?
450 host = "#{::Socket.gethostname}.redmine" if host.empty?
446 "<#{hash}@#{host}>"
451 "<#{hash}@#{host}>"
447 end
452 end
448
453
449 private
454 private
450
455
451 def message_id(object)
456 def message_id(object)
452 @message_id_object = object
457 @message_id_object = object
453 end
458 end
454
459
455 def references(object)
460 def references(object)
456 @references_objects ||= []
461 @references_objects ||= []
457 @references_objects << object
462 @references_objects << object
458 end
463 end
459
464
460 def mylogger
465 def mylogger
461 Rails.logger
466 Rails.logger
462 end
467 end
463 end
468 end
464
469
465 # Patch TMail so that message_id is not overwritten
470 # Patch TMail so that message_id is not overwritten
466 module TMail
471 module TMail
467 class Mail
472 class Mail
468 def add_message_id( fqdn = nil )
473 def add_message_id( fqdn = nil )
469 self.message_id ||= ::TMail::new_message_id(fqdn)
474 self.message_id ||= ::TMail::new_message_id(fqdn)
470 end
475 end
471 end
476 end
472 end
477 end
@@ -1,510 +1,518
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class MailerTest < ActiveSupport::TestCase
20 class MailerTest < ActiveSupport::TestCase
21 include Redmine::I18n
21 include Redmine::I18n
22 include ActionController::Assertions::SelectorAssertions
22 include ActionController::Assertions::SelectorAssertions
23 fixtures :projects, :enabled_modules, :issues, :users, :members,
23 fixtures :projects, :enabled_modules, :issues, :users, :members,
24 :member_roles, :roles, :documents, :attachments, :news,
24 :member_roles, :roles, :documents, :attachments, :news,
25 :tokens, :journals, :journal_details, :changesets, :trackers,
25 :tokens, :journals, :journal_details, :changesets, :trackers,
26 :issue_statuses, :enumerations, :messages, :boards, :repositories,
26 :issue_statuses, :enumerations, :messages, :boards, :repositories,
27 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
27 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
28 :versions,
28 :versions,
29 :comments
29 :comments
30
30
31 def setup
31 def setup
32 ActionMailer::Base.deliveries.clear
32 ActionMailer::Base.deliveries.clear
33 Setting.host_name = 'mydomain.foo'
33 Setting.host_name = 'mydomain.foo'
34 Setting.protocol = 'http'
34 Setting.protocol = 'http'
35 Setting.plain_text_mail = '0'
35 Setting.plain_text_mail = '0'
36 end
36 end
37
37
38 def test_generated_links_in_emails
38 def test_generated_links_in_emails
39 Setting.host_name = 'mydomain.foo'
39 Setting.host_name = 'mydomain.foo'
40 Setting.protocol = 'https'
40 Setting.protocol = 'https'
41
41
42 journal = Journal.find(2)
42 journal = Journal.find(2)
43 assert Mailer.deliver_issue_edit(journal)
43 assert Mailer.deliver_issue_edit(journal)
44
44
45 mail = ActionMailer::Base.deliveries.last
45 mail = ActionMailer::Base.deliveries.last
46 assert_kind_of TMail::Mail, mail
46 assert_kind_of TMail::Mail, mail
47
47
48 assert_select_email do
48 assert_select_email do
49 # link to the main ticket
49 # link to the main ticket
50 assert_select "a[href=?]",
50 assert_select "a[href=?]",
51 "https://mydomain.foo/issues/1#change-2",
51 "https://mydomain.foo/issues/1#change-2",
52 :text => "Bug #1: Can't print recipes"
52 :text => "Bug #1: Can't print recipes"
53 # link to a referenced ticket
53 # link to a referenced ticket
54 assert_select "a[href=?][title=?]",
54 assert_select "a[href=?][title=?]",
55 "https://mydomain.foo/issues/2",
55 "https://mydomain.foo/issues/2",
56 "Add ingredients categories (Assigned)",
56 "Add ingredients categories (Assigned)",
57 :text => "#2"
57 :text => "#2"
58 # link to a changeset
58 # link to a changeset
59 assert_select "a[href=?][title=?]",
59 assert_select "a[href=?][title=?]",
60 "https://mydomain.foo/projects/ecookbook/repository/revisions/2",
60 "https://mydomain.foo/projects/ecookbook/repository/revisions/2",
61 "This commit fixes #1, #2 and references #1 &amp; #3",
61 "This commit fixes #1, #2 and references #1 &amp; #3",
62 :text => "r2"
62 :text => "r2"
63 end
63 end
64 end
64 end
65
65
66 def test_generated_links_with_prefix
66 def test_generated_links_with_prefix
67 relative_url_root = Redmine::Utils.relative_url_root
67 relative_url_root = Redmine::Utils.relative_url_root
68 Setting.host_name = 'mydomain.foo/rdm'
68 Setting.host_name = 'mydomain.foo/rdm'
69 Setting.protocol = 'http'
69 Setting.protocol = 'http'
70 Redmine::Utils.relative_url_root = '/rdm'
70 Redmine::Utils.relative_url_root = '/rdm'
71
71
72 journal = Journal.find(2)
72 journal = Journal.find(2)
73 assert Mailer.deliver_issue_edit(journal)
73 assert Mailer.deliver_issue_edit(journal)
74
74
75 mail = ActionMailer::Base.deliveries.last
75 mail = ActionMailer::Base.deliveries.last
76 assert_kind_of TMail::Mail, mail
76 assert_kind_of TMail::Mail, mail
77
77
78 assert_select_email do
78 assert_select_email do
79 # link to the main ticket
79 # link to the main ticket
80 assert_select "a[href=?]",
80 assert_select "a[href=?]",
81 "http://mydomain.foo/rdm/issues/1#change-2",
81 "http://mydomain.foo/rdm/issues/1#change-2",
82 :text => "Bug #1: Can't print recipes"
82 :text => "Bug #1: Can't print recipes"
83 # link to a referenced ticket
83 # link to a referenced ticket
84 assert_select "a[href=?][title=?]",
84 assert_select "a[href=?][title=?]",
85 "http://mydomain.foo/rdm/issues/2",
85 "http://mydomain.foo/rdm/issues/2",
86 "Add ingredients categories (Assigned)",
86 "Add ingredients categories (Assigned)",
87 :text => "#2"
87 :text => "#2"
88 # link to a changeset
88 # link to a changeset
89 assert_select "a[href=?][title=?]",
89 assert_select "a[href=?][title=?]",
90 "http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2",
90 "http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2",
91 "This commit fixes #1, #2 and references #1 &amp; #3",
91 "This commit fixes #1, #2 and references #1 &amp; #3",
92 :text => "r2"
92 :text => "r2"
93 end
93 end
94 ensure
94 ensure
95 # restore it
95 # restore it
96 Redmine::Utils.relative_url_root = relative_url_root
96 Redmine::Utils.relative_url_root = relative_url_root
97 end
97 end
98
98
99 def test_generated_links_with_prefix_and_no_relative_url_root
99 def test_generated_links_with_prefix_and_no_relative_url_root
100 relative_url_root = Redmine::Utils.relative_url_root
100 relative_url_root = Redmine::Utils.relative_url_root
101 Setting.host_name = 'mydomain.foo/rdm'
101 Setting.host_name = 'mydomain.foo/rdm'
102 Setting.protocol = 'http'
102 Setting.protocol = 'http'
103 Redmine::Utils.relative_url_root = nil
103 Redmine::Utils.relative_url_root = nil
104
104
105 journal = Journal.find(2)
105 journal = Journal.find(2)
106 assert Mailer.deliver_issue_edit(journal)
106 assert Mailer.deliver_issue_edit(journal)
107
107
108 mail = ActionMailer::Base.deliveries.last
108 mail = ActionMailer::Base.deliveries.last
109 assert_kind_of TMail::Mail, mail
109 assert_kind_of TMail::Mail, mail
110
110
111 assert_select_email do
111 assert_select_email do
112 # link to the main ticket
112 # link to the main ticket
113 assert_select "a[href=?]",
113 assert_select "a[href=?]",
114 "http://mydomain.foo/rdm/issues/1#change-2",
114 "http://mydomain.foo/rdm/issues/1#change-2",
115 :text => "Bug #1: Can't print recipes"
115 :text => "Bug #1: Can't print recipes"
116 # link to a referenced ticket
116 # link to a referenced ticket
117 assert_select "a[href=?][title=?]",
117 assert_select "a[href=?][title=?]",
118 "http://mydomain.foo/rdm/issues/2",
118 "http://mydomain.foo/rdm/issues/2",
119 "Add ingredients categories (Assigned)",
119 "Add ingredients categories (Assigned)",
120 :text => "#2"
120 :text => "#2"
121 # link to a changeset
121 # link to a changeset
122 assert_select "a[href=?][title=?]",
122 assert_select "a[href=?][title=?]",
123 "http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2",
123 "http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2",
124 "This commit fixes #1, #2 and references #1 &amp; #3",
124 "This commit fixes #1, #2 and references #1 &amp; #3",
125 :text => "r2"
125 :text => "r2"
126 end
126 end
127 ensure
127 ensure
128 # restore it
128 # restore it
129 Redmine::Utils.relative_url_root = relative_url_root
129 Redmine::Utils.relative_url_root = relative_url_root
130 end
130 end
131
131
132 def test_email_headers
132 def test_email_headers
133 issue = Issue.find(1)
133 issue = Issue.find(1)
134 Mailer.deliver_issue_add(issue)
134 Mailer.deliver_issue_add(issue)
135 mail = ActionMailer::Base.deliveries.last
135 mail = ActionMailer::Base.deliveries.last
136 assert_not_nil mail
136 assert_not_nil mail
137 assert_equal 'OOF', mail.header_string('X-Auto-Response-Suppress')
137 assert_equal 'OOF', mail.header_string('X-Auto-Response-Suppress')
138 assert_equal 'auto-generated', mail.header_string('Auto-Submitted')
138 assert_equal 'auto-generated', mail.header_string('Auto-Submitted')
139 end
139 end
140
140
141 def test_email_headers_should_include_sender
142 issue = Issue.find(1)
143 Mailer.deliver_issue_add(issue)
144 mail = ActionMailer::Base.deliveries.last
145 assert_not_nil mail
146 assert_equal issue.author.login, mail.header_string('X-Redmine-Sender')
147 end
148
141 def test_plain_text_mail
149 def test_plain_text_mail
142 Setting.plain_text_mail = 1
150 Setting.plain_text_mail = 1
143 journal = Journal.find(2)
151 journal = Journal.find(2)
144 Mailer.deliver_issue_edit(journal)
152 Mailer.deliver_issue_edit(journal)
145 mail = ActionMailer::Base.deliveries.last
153 mail = ActionMailer::Base.deliveries.last
146 assert_equal "text/plain", mail.content_type
154 assert_equal "text/plain", mail.content_type
147 assert_equal 0, mail.parts.size
155 assert_equal 0, mail.parts.size
148 assert !mail.encoded.include?('href')
156 assert !mail.encoded.include?('href')
149 end
157 end
150
158
151 def test_html_mail
159 def test_html_mail
152 Setting.plain_text_mail = 0
160 Setting.plain_text_mail = 0
153 journal = Journal.find(2)
161 journal = Journal.find(2)
154 Mailer.deliver_issue_edit(journal)
162 Mailer.deliver_issue_edit(journal)
155 mail = ActionMailer::Base.deliveries.last
163 mail = ActionMailer::Base.deliveries.last
156 assert_equal 2, mail.parts.size
164 assert_equal 2, mail.parts.size
157 assert mail.encoded.include?('href')
165 assert mail.encoded.include?('href')
158 end
166 end
159
167
160 def test_from_header
168 def test_from_header
161 with_settings :mail_from => 'redmine@example.net' do
169 with_settings :mail_from => 'redmine@example.net' do
162 Mailer.deliver_test(User.find(1))
170 Mailer.deliver_test(User.find(1))
163 end
171 end
164 mail = ActionMailer::Base.deliveries.last
172 mail = ActionMailer::Base.deliveries.last
165 assert_not_nil mail
173 assert_not_nil mail
166 assert_equal 'redmine@example.net', mail.from_addrs.first.address
174 assert_equal 'redmine@example.net', mail.from_addrs.first.address
167 end
175 end
168
176
169 def test_from_header_with_phrase
177 def test_from_header_with_phrase
170 with_settings :mail_from => 'Redmine app <redmine@example.net>' do
178 with_settings :mail_from => 'Redmine app <redmine@example.net>' do
171 Mailer.deliver_test(User.find(1))
179 Mailer.deliver_test(User.find(1))
172 end
180 end
173 mail = ActionMailer::Base.deliveries.last
181 mail = ActionMailer::Base.deliveries.last
174 assert_not_nil mail
182 assert_not_nil mail
175 assert_equal 'redmine@example.net', mail.from_addrs.first.address
183 assert_equal 'redmine@example.net', mail.from_addrs.first.address
176 assert_equal 'Redmine app', mail.from_addrs.first.name
184 assert_equal 'Redmine app', mail.from_addrs.first.name
177 end
185 end
178
186
179 def test_should_not_send_email_without_recipient
187 def test_should_not_send_email_without_recipient
180 news = News.find(:first)
188 news = News.find(:first)
181 user = news.author
189 user = news.author
182 # Remove members except news author
190 # Remove members except news author
183 news.project.memberships.each {|m| m.destroy unless m.user == user}
191 news.project.memberships.each {|m| m.destroy unless m.user == user}
184
192
185 user.pref[:no_self_notified] = false
193 user.pref[:no_self_notified] = false
186 user.pref.save
194 user.pref.save
187 User.current = user
195 User.current = user
188 Mailer.deliver_news_added(news.reload)
196 Mailer.deliver_news_added(news.reload)
189 assert_equal 1, last_email.bcc.size
197 assert_equal 1, last_email.bcc.size
190
198
191 # nobody to notify
199 # nobody to notify
192 user.pref[:no_self_notified] = true
200 user.pref[:no_self_notified] = true
193 user.pref.save
201 user.pref.save
194 User.current = user
202 User.current = user
195 ActionMailer::Base.deliveries.clear
203 ActionMailer::Base.deliveries.clear
196 Mailer.deliver_news_added(news.reload)
204 Mailer.deliver_news_added(news.reload)
197 assert ActionMailer::Base.deliveries.empty?
205 assert ActionMailer::Base.deliveries.empty?
198 end
206 end
199
207
200 def test_issue_add_message_id
208 def test_issue_add_message_id
201 issue = Issue.find(1)
209 issue = Issue.find(1)
202 Mailer.deliver_issue_add(issue)
210 Mailer.deliver_issue_add(issue)
203 mail = ActionMailer::Base.deliveries.last
211 mail = ActionMailer::Base.deliveries.last
204 assert_not_nil mail
212 assert_not_nil mail
205 assert_equal Mailer.message_id_for(issue), mail.message_id
213 assert_equal Mailer.message_id_for(issue), mail.message_id
206 assert_nil mail.references
214 assert_nil mail.references
207 end
215 end
208
216
209 def test_issue_edit_message_id
217 def test_issue_edit_message_id
210 journal = Journal.find(1)
218 journal = Journal.find(1)
211 Mailer.deliver_issue_edit(journal)
219 Mailer.deliver_issue_edit(journal)
212 mail = ActionMailer::Base.deliveries.last
220 mail = ActionMailer::Base.deliveries.last
213 assert_not_nil mail
221 assert_not_nil mail
214 assert_equal Mailer.message_id_for(journal), mail.message_id
222 assert_equal Mailer.message_id_for(journal), mail.message_id
215 assert_equal Mailer.message_id_for(journal.issue), mail.references.first.to_s
223 assert_equal Mailer.message_id_for(journal.issue), mail.references.first.to_s
216 assert_select_email do
224 assert_select_email do
217 # link to the update
225 # link to the update
218 assert_select "a[href=?]",
226 assert_select "a[href=?]",
219 "http://mydomain.foo/issues/#{journal.journalized_id}#change-#{journal.id}"
227 "http://mydomain.foo/issues/#{journal.journalized_id}#change-#{journal.id}"
220 end
228 end
221 end
229 end
222
230
223 def test_message_posted_message_id
231 def test_message_posted_message_id
224 message = Message.find(1)
232 message = Message.find(1)
225 Mailer.deliver_message_posted(message)
233 Mailer.deliver_message_posted(message)
226 mail = ActionMailer::Base.deliveries.last
234 mail = ActionMailer::Base.deliveries.last
227 assert_not_nil mail
235 assert_not_nil mail
228 assert_equal Mailer.message_id_for(message), mail.message_id
236 assert_equal Mailer.message_id_for(message), mail.message_id
229 assert_nil mail.references
237 assert_nil mail.references
230 assert_select_email do
238 assert_select_email do
231 # link to the message
239 # link to the message
232 assert_select "a[href=?]",
240 assert_select "a[href=?]",
233 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}",
241 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}",
234 :text => message.subject
242 :text => message.subject
235 end
243 end
236 end
244 end
237
245
238 def test_reply_posted_message_id
246 def test_reply_posted_message_id
239 message = Message.find(3)
247 message = Message.find(3)
240 Mailer.deliver_message_posted(message)
248 Mailer.deliver_message_posted(message)
241 mail = ActionMailer::Base.deliveries.last
249 mail = ActionMailer::Base.deliveries.last
242 assert_not_nil mail
250 assert_not_nil mail
243 assert_equal Mailer.message_id_for(message), mail.message_id
251 assert_equal Mailer.message_id_for(message), mail.message_id
244 assert_equal Mailer.message_id_for(message.parent), mail.references.first.to_s
252 assert_equal Mailer.message_id_for(message.parent), mail.references.first.to_s
245 assert_select_email do
253 assert_select_email do
246 # link to the reply
254 # link to the reply
247 assert_select "a[href=?]",
255 assert_select "a[href=?]",
248 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.root.id}?r=#{message.id}#message-#{message.id}",
256 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.root.id}?r=#{message.id}#message-#{message.id}",
249 :text => message.subject
257 :text => message.subject
250 end
258 end
251 end
259 end
252
260
253 context("#issue_add") do
261 context("#issue_add") do
254 setup do
262 setup do
255 ActionMailer::Base.deliveries.clear
263 ActionMailer::Base.deliveries.clear
256 Setting.bcc_recipients = '1'
264 Setting.bcc_recipients = '1'
257 @issue = Issue.find(1)
265 @issue = Issue.find(1)
258 end
266 end
259
267
260 should "notify project members" do
268 should "notify project members" do
261 assert Mailer.deliver_issue_add(@issue)
269 assert Mailer.deliver_issue_add(@issue)
262 assert last_email.bcc.include?('dlopper@somenet.foo')
270 assert last_email.bcc.include?('dlopper@somenet.foo')
263 end
271 end
264
272
265 should "not notify project members that are not allow to view the issue" do
273 should "not notify project members that are not allow to view the issue" do
266 Role.find(2).remove_permission!(:view_issues)
274 Role.find(2).remove_permission!(:view_issues)
267 assert Mailer.deliver_issue_add(@issue)
275 assert Mailer.deliver_issue_add(@issue)
268 assert !last_email.bcc.include?('dlopper@somenet.foo')
276 assert !last_email.bcc.include?('dlopper@somenet.foo')
269 end
277 end
270
278
271 should "notify issue watchers" do
279 should "notify issue watchers" do
272 user = User.find(9)
280 user = User.find(9)
273 # minimal email notification options
281 # minimal email notification options
274 user.pref[:no_self_notified] = '1'
282 user.pref[:no_self_notified] = '1'
275 user.pref.save
283 user.pref.save
276 user.mail_notification = false
284 user.mail_notification = false
277 user.save
285 user.save
278
286
279 Watcher.create!(:watchable => @issue, :user => user)
287 Watcher.create!(:watchable => @issue, :user => user)
280 assert Mailer.deliver_issue_add(@issue)
288 assert Mailer.deliver_issue_add(@issue)
281 assert last_email.bcc.include?(user.mail)
289 assert last_email.bcc.include?(user.mail)
282 end
290 end
283
291
284 should "not notify watchers not allowed to view the issue" do
292 should "not notify watchers not allowed to view the issue" do
285 user = User.find(9)
293 user = User.find(9)
286 Watcher.create!(:watchable => @issue, :user => user)
294 Watcher.create!(:watchable => @issue, :user => user)
287 Role.non_member.remove_permission!(:view_issues)
295 Role.non_member.remove_permission!(:view_issues)
288 assert Mailer.deliver_issue_add(@issue)
296 assert Mailer.deliver_issue_add(@issue)
289 assert !last_email.bcc.include?(user.mail)
297 assert !last_email.bcc.include?(user.mail)
290 end
298 end
291 end
299 end
292
300
293 # test mailer methods for each language
301 # test mailer methods for each language
294 def test_issue_add
302 def test_issue_add
295 issue = Issue.find(1)
303 issue = Issue.find(1)
296 valid_languages.each do |lang|
304 valid_languages.each do |lang|
297 Setting.default_language = lang.to_s
305 Setting.default_language = lang.to_s
298 assert Mailer.deliver_issue_add(issue)
306 assert Mailer.deliver_issue_add(issue)
299 end
307 end
300 end
308 end
301
309
302 def test_issue_edit
310 def test_issue_edit
303 journal = Journal.find(1)
311 journal = Journal.find(1)
304 valid_languages.each do |lang|
312 valid_languages.each do |lang|
305 Setting.default_language = lang.to_s
313 Setting.default_language = lang.to_s
306 assert Mailer.deliver_issue_edit(journal)
314 assert Mailer.deliver_issue_edit(journal)
307 end
315 end
308 end
316 end
309
317
310 def test_document_added
318 def test_document_added
311 document = Document.find(1)
319 document = Document.find(1)
312 valid_languages.each do |lang|
320 valid_languages.each do |lang|
313 Setting.default_language = lang.to_s
321 Setting.default_language = lang.to_s
314 assert Mailer.deliver_document_added(document)
322 assert Mailer.deliver_document_added(document)
315 end
323 end
316 end
324 end
317
325
318 def test_attachments_added
326 def test_attachments_added
319 attachements = [ Attachment.find_by_container_type('Document') ]
327 attachements = [ Attachment.find_by_container_type('Document') ]
320 valid_languages.each do |lang|
328 valid_languages.each do |lang|
321 Setting.default_language = lang.to_s
329 Setting.default_language = lang.to_s
322 assert Mailer.deliver_attachments_added(attachements)
330 assert Mailer.deliver_attachments_added(attachements)
323 end
331 end
324 end
332 end
325
333
326 def test_version_file_added
334 def test_version_file_added
327 attachements = [ Attachment.find_by_container_type('Version') ]
335 attachements = [ Attachment.find_by_container_type('Version') ]
328 assert Mailer.deliver_attachments_added(attachements)
336 assert Mailer.deliver_attachments_added(attachements)
329 assert_not_nil last_email.bcc
337 assert_not_nil last_email.bcc
330 assert last_email.bcc.any?
338 assert last_email.bcc.any?
331 assert_select_email do
339 assert_select_email do
332 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
340 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
333 end
341 end
334 end
342 end
335
343
336 def test_project_file_added
344 def test_project_file_added
337 attachements = [ Attachment.find_by_container_type('Project') ]
345 attachements = [ Attachment.find_by_container_type('Project') ]
338 assert Mailer.deliver_attachments_added(attachements)
346 assert Mailer.deliver_attachments_added(attachements)
339 assert_not_nil last_email.bcc
347 assert_not_nil last_email.bcc
340 assert last_email.bcc.any?
348 assert last_email.bcc.any?
341 assert_select_email do
349 assert_select_email do
342 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
350 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
343 end
351 end
344 end
352 end
345
353
346 def test_news_added
354 def test_news_added
347 news = News.find(:first)
355 news = News.find(:first)
348 valid_languages.each do |lang|
356 valid_languages.each do |lang|
349 Setting.default_language = lang.to_s
357 Setting.default_language = lang.to_s
350 assert Mailer.deliver_news_added(news)
358 assert Mailer.deliver_news_added(news)
351 end
359 end
352 end
360 end
353
361
354 def test_news_comment_added
362 def test_news_comment_added
355 comment = Comment.find(2)
363 comment = Comment.find(2)
356 valid_languages.each do |lang|
364 valid_languages.each do |lang|
357 Setting.default_language = lang.to_s
365 Setting.default_language = lang.to_s
358 assert Mailer.deliver_news_comment_added(comment)
366 assert Mailer.deliver_news_comment_added(comment)
359 end
367 end
360 end
368 end
361
369
362 def test_message_posted
370 def test_message_posted
363 message = Message.find(:first)
371 message = Message.find(:first)
364 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
372 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
365 recipients = recipients.compact.uniq
373 recipients = recipients.compact.uniq
366 valid_languages.each do |lang|
374 valid_languages.each do |lang|
367 Setting.default_language = lang.to_s
375 Setting.default_language = lang.to_s
368 assert Mailer.deliver_message_posted(message)
376 assert Mailer.deliver_message_posted(message)
369 end
377 end
370 end
378 end
371
379
372 def test_wiki_content_added
380 def test_wiki_content_added
373 content = WikiContent.find(:first)
381 content = WikiContent.find(:first)
374 valid_languages.each do |lang|
382 valid_languages.each do |lang|
375 Setting.default_language = lang.to_s
383 Setting.default_language = lang.to_s
376 assert_difference 'ActionMailer::Base.deliveries.size' do
384 assert_difference 'ActionMailer::Base.deliveries.size' do
377 assert Mailer.deliver_wiki_content_added(content)
385 assert Mailer.deliver_wiki_content_added(content)
378 end
386 end
379 end
387 end
380 end
388 end
381
389
382 def test_wiki_content_updated
390 def test_wiki_content_updated
383 content = WikiContent.find(:first)
391 content = WikiContent.find(:first)
384 valid_languages.each do |lang|
392 valid_languages.each do |lang|
385 Setting.default_language = lang.to_s
393 Setting.default_language = lang.to_s
386 assert_difference 'ActionMailer::Base.deliveries.size' do
394 assert_difference 'ActionMailer::Base.deliveries.size' do
387 assert Mailer.deliver_wiki_content_updated(content)
395 assert Mailer.deliver_wiki_content_updated(content)
388 end
396 end
389 end
397 end
390 end
398 end
391
399
392 def test_account_information
400 def test_account_information
393 user = User.find(2)
401 user = User.find(2)
394 valid_languages.each do |lang|
402 valid_languages.each do |lang|
395 user.update_attribute :language, lang.to_s
403 user.update_attribute :language, lang.to_s
396 user.reload
404 user.reload
397 assert Mailer.deliver_account_information(user, 'pAsswORd')
405 assert Mailer.deliver_account_information(user, 'pAsswORd')
398 end
406 end
399 end
407 end
400
408
401 def test_lost_password
409 def test_lost_password
402 token = Token.find(2)
410 token = Token.find(2)
403 valid_languages.each do |lang|
411 valid_languages.each do |lang|
404 token.user.update_attribute :language, lang.to_s
412 token.user.update_attribute :language, lang.to_s
405 token.reload
413 token.reload
406 assert Mailer.deliver_lost_password(token)
414 assert Mailer.deliver_lost_password(token)
407 end
415 end
408 end
416 end
409
417
410 def test_register
418 def test_register
411 token = Token.find(1)
419 token = Token.find(1)
412 Setting.host_name = 'redmine.foo'
420 Setting.host_name = 'redmine.foo'
413 Setting.protocol = 'https'
421 Setting.protocol = 'https'
414
422
415 valid_languages.each do |lang|
423 valid_languages.each do |lang|
416 token.user.update_attribute :language, lang.to_s
424 token.user.update_attribute :language, lang.to_s
417 token.reload
425 token.reload
418 ActionMailer::Base.deliveries.clear
426 ActionMailer::Base.deliveries.clear
419 assert Mailer.deliver_register(token)
427 assert Mailer.deliver_register(token)
420 mail = ActionMailer::Base.deliveries.last
428 mail = ActionMailer::Base.deliveries.last
421 assert mail.body.include?("https://redmine.foo/account/activate?token=#{token.value}")
429 assert mail.body.include?("https://redmine.foo/account/activate?token=#{token.value}")
422 end
430 end
423 end
431 end
424
432
425 def test_test
433 def test_test
426 user = User.find(1)
434 user = User.find(1)
427 valid_languages.each do |lang|
435 valid_languages.each do |lang|
428 user.update_attribute :language, lang.to_s
436 user.update_attribute :language, lang.to_s
429 assert Mailer.deliver_test(user)
437 assert Mailer.deliver_test(user)
430 end
438 end
431 end
439 end
432
440
433 def test_reminders
441 def test_reminders
434 Mailer.reminders(:days => 42)
442 Mailer.reminders(:days => 42)
435 assert_equal 1, ActionMailer::Base.deliveries.size
443 assert_equal 1, ActionMailer::Base.deliveries.size
436 mail = ActionMailer::Base.deliveries.last
444 mail = ActionMailer::Base.deliveries.last
437 assert mail.bcc.include?('dlopper@somenet.foo')
445 assert mail.bcc.include?('dlopper@somenet.foo')
438 assert mail.body.include?('Bug #3: Error 281 when updating a recipe')
446 assert mail.body.include?('Bug #3: Error 281 when updating a recipe')
439 assert_equal '1 issue(s) due in the next 42 days', mail.subject
447 assert_equal '1 issue(s) due in the next 42 days', mail.subject
440 end
448 end
441
449
442 def test_reminders_should_not_include_closed_issues
450 def test_reminders_should_not_include_closed_issues
443 Issue.generate!(:project_id => 1, :tracker_id => 1, :status_id => 5, :subject => 'Closed issue', :assigned_to_id => 3, :due_date => 5.days.from_now)
451 Issue.generate!(:project_id => 1, :tracker_id => 1, :status_id => 5, :subject => 'Closed issue', :assigned_to_id => 3, :due_date => 5.days.from_now)
444 ActionMailer::Base.deliveries.clear
452 ActionMailer::Base.deliveries.clear
445
453
446 Mailer.reminders(:days => 42)
454 Mailer.reminders(:days => 42)
447 assert_equal 1, ActionMailer::Base.deliveries.size
455 assert_equal 1, ActionMailer::Base.deliveries.size
448 mail = ActionMailer::Base.deliveries.last
456 mail = ActionMailer::Base.deliveries.last
449 assert mail.bcc.include?('dlopper@somenet.foo')
457 assert mail.bcc.include?('dlopper@somenet.foo')
450 assert !mail.body.include?('Closed issue')
458 assert !mail.body.include?('Closed issue')
451 end
459 end
452
460
453 def test_reminders_for_users
461 def test_reminders_for_users
454 Mailer.reminders(:days => 42, :users => ['5'])
462 Mailer.reminders(:days => 42, :users => ['5'])
455 assert_equal 0, ActionMailer::Base.deliveries.size # No mail for dlopper
463 assert_equal 0, ActionMailer::Base.deliveries.size # No mail for dlopper
456 Mailer.reminders(:days => 42, :users => ['3'])
464 Mailer.reminders(:days => 42, :users => ['3'])
457 assert_equal 1, ActionMailer::Base.deliveries.size # No mail for dlopper
465 assert_equal 1, ActionMailer::Base.deliveries.size # No mail for dlopper
458 mail = ActionMailer::Base.deliveries.last
466 mail = ActionMailer::Base.deliveries.last
459 assert mail.bcc.include?('dlopper@somenet.foo')
467 assert mail.bcc.include?('dlopper@somenet.foo')
460 assert mail.body.include?('Bug #3: Error 281 when updating a recipe')
468 assert mail.body.include?('Bug #3: Error 281 when updating a recipe')
461 end
469 end
462
470
463 def last_email
471 def last_email
464 mail = ActionMailer::Base.deliveries.last
472 mail = ActionMailer::Base.deliveries.last
465 assert_not_nil mail
473 assert_not_nil mail
466 mail
474 mail
467 end
475 end
468
476
469 def test_mailer_should_not_change_locale
477 def test_mailer_should_not_change_locale
470 Setting.default_language = 'en'
478 Setting.default_language = 'en'
471 # Set current language to italian
479 # Set current language to italian
472 set_language_if_valid 'it'
480 set_language_if_valid 'it'
473 # Send an email to a french user
481 # Send an email to a french user
474 user = User.find(1)
482 user = User.find(1)
475 user.language = 'fr'
483 user.language = 'fr'
476 Mailer.deliver_account_activated(user)
484 Mailer.deliver_account_activated(user)
477 mail = ActionMailer::Base.deliveries.last
485 mail = ActionMailer::Base.deliveries.last
478 assert mail.body.include?('Votre compte')
486 assert mail.body.include?('Votre compte')
479
487
480 assert_equal :it, current_language
488 assert_equal :it, current_language
481 end
489 end
482
490
483 def test_with_deliveries_off
491 def test_with_deliveries_off
484 Mailer.with_deliveries false do
492 Mailer.with_deliveries false do
485 Mailer.deliver_test(User.find(1))
493 Mailer.deliver_test(User.find(1))
486 end
494 end
487 assert ActionMailer::Base.deliveries.empty?
495 assert ActionMailer::Base.deliveries.empty?
488 # should restore perform_deliveries
496 # should restore perform_deliveries
489 assert ActionMailer::Base.perform_deliveries
497 assert ActionMailer::Base.perform_deliveries
490 end
498 end
491
499
492 def test_tmail_to_header_field_should_not_include_blank_lines
500 def test_tmail_to_header_field_should_not_include_blank_lines
493 mail = TMail::Mail.new
501 mail = TMail::Mail.new
494 mail.to = ["a.user@example.com", "v.user2@example.com", "e.smith@example.com", "info@example.com", "v.pupkin@example.com",
502 mail.to = ["a.user@example.com", "v.user2@example.com", "e.smith@example.com", "info@example.com", "v.pupkin@example.com",
495 "b.user@example.com", "w.user2@example.com", "f.smith@example.com", "info2@example.com", "w.pupkin@example.com"]
503 "b.user@example.com", "w.user2@example.com", "f.smith@example.com", "info2@example.com", "w.pupkin@example.com"]
496
504
497 assert !mail.encoded.strip.split("\r\n").detect(&:blank?), "#{mail.encoded} malformed"
505 assert !mail.encoded.strip.split("\r\n").detect(&:blank?), "#{mail.encoded} malformed"
498 end
506 end
499
507
500 def test_layout_should_include_the_emails_header
508 def test_layout_should_include_the_emails_header
501 with_settings :emails_header => "*Header content*" do
509 with_settings :emails_header => "*Header content*" do
502 assert Mailer.deliver_test(User.find(1))
510 assert Mailer.deliver_test(User.find(1))
503 assert_select_email do
511 assert_select_email do
504 assert_select ".header" do
512 assert_select ".header" do
505 assert_select "strong", :text => "Header content"
513 assert_select "strong", :text => "Header content"
506 end
514 end
507 end
515 end
508 end
516 end
509 end
517 end
510 end
518 end
General Comments 0
You need to be logged in to leave comments. Login now