##// END OF EJS Templates
Merged r14159 (#19558)....
Jean-Philippe Lang -
r13827:214e737ee02c
parent child
Show More
@@ -1,551 +1,550
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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 MailHandler < ActionMailer::Base
18 class MailHandler < ActionMailer::Base
19 include ActionView::Helpers::SanitizeHelper
19 include ActionView::Helpers::SanitizeHelper
20 include Redmine::I18n
20 include Redmine::I18n
21
21
22 class UnauthorizedAction < StandardError; end
22 class UnauthorizedAction < StandardError; end
23 class MissingInformation < StandardError; end
23 class MissingInformation < StandardError; end
24
24
25 attr_reader :email, :user
25 attr_reader :email, :user
26
26
27 def self.receive(email, options={})
27 def self.receive(email, options={})
28 @@handler_options = options.deep_dup
28 @@handler_options = options.deep_dup
29
29
30 @@handler_options[:issue] ||= {}
30 @@handler_options[:issue] ||= {}
31
31
32 if @@handler_options[:allow_override].is_a?(String)
32 if @@handler_options[:allow_override].is_a?(String)
33 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip)
33 @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip)
34 end
34 end
35 @@handler_options[:allow_override] ||= []
35 @@handler_options[:allow_override] ||= []
36 # Project needs to be overridable if not specified
36 # Project needs to be overridable if not specified
37 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
37 @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
38 # Status overridable by default
38 # Status overridable by default
39 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
39 @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status)
40
40
41 @@handler_options[:no_account_notice] = (@@handler_options[:no_account_notice].to_s == '1')
41 @@handler_options[:no_account_notice] = (@@handler_options[:no_account_notice].to_s == '1')
42 @@handler_options[:no_notification] = (@@handler_options[:no_notification].to_s == '1')
42 @@handler_options[:no_notification] = (@@handler_options[:no_notification].to_s == '1')
43 @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1')
43 @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1')
44
44
45 email.force_encoding('ASCII-8BIT')
45 email.force_encoding('ASCII-8BIT')
46 super(email)
46 super(email)
47 end
47 end
48
48
49 # Receives an email and rescues any exception
49 # Receives an email and rescues any exception
50 def self.safe_receive(*args)
50 def self.safe_receive(*args)
51 receive(*args)
51 receive(*args)
52 rescue Exception => e
52 rescue Exception => e
53 logger.error "An unexpected error occurred when receiving email: #{e.message}" if logger
53 logger.error "An unexpected error occurred when receiving email: #{e.message}" if logger
54 return false
54 return false
55 end
55 end
56
56
57 # Extracts MailHandler options from environment variables
57 # Extracts MailHandler options from environment variables
58 # Use when receiving emails with rake tasks
58 # Use when receiving emails with rake tasks
59 def self.extract_options_from_env(env)
59 def self.extract_options_from_env(env)
60 options = {:issue => {}}
60 options = {:issue => {}}
61 %w(project status tracker category priority).each do |option|
61 %w(project status tracker category priority).each do |option|
62 options[:issue][option.to_sym] = env[option] if env[option]
62 options[:issue][option.to_sym] = env[option] if env[option]
63 end
63 end
64 %w(allow_override unknown_user no_permission_check no_account_notice default_group).each do |option|
64 %w(allow_override unknown_user no_permission_check no_account_notice default_group).each do |option|
65 options[option.to_sym] = env[option] if env[option]
65 options[option.to_sym] = env[option] if env[option]
66 end
66 end
67 options
67 options
68 end
68 end
69
69
70 def logger
70 def logger
71 Rails.logger
71 Rails.logger
72 end
72 end
73
73
74 cattr_accessor :ignored_emails_headers
74 cattr_accessor :ignored_emails_headers
75 @@ignored_emails_headers = {
75 @@ignored_emails_headers = {
76 'X-Auto-Response-Suppress' => /(oof|all)/,
77 'Auto-Submitted' => /\Aauto-(replied|generated)/,
76 'Auto-Submitted' => /\Aauto-(replied|generated)/,
78 'X-Autoreply' => 'yes'
77 'X-Autoreply' => 'yes'
79 }
78 }
80
79
81 # Processes incoming emails
80 # Processes incoming emails
82 # Returns the created object (eg. an issue, a message) or false
81 # Returns the created object (eg. an issue, a message) or false
83 def receive(email)
82 def receive(email)
84 @email = email
83 @email = email
85 sender_email = email.from.to_a.first.to_s.strip
84 sender_email = email.from.to_a.first.to_s.strip
86 # Ignore emails received from the application emission address to avoid hell cycles
85 # Ignore emails received from the application emission address to avoid hell cycles
87 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
86 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
88 if logger
87 if logger
89 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
88 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
90 end
89 end
91 return false
90 return false
92 end
91 end
93 # Ignore auto generated emails
92 # Ignore auto generated emails
94 self.class.ignored_emails_headers.each do |key, ignored_value|
93 self.class.ignored_emails_headers.each do |key, ignored_value|
95 value = email.header[key]
94 value = email.header[key]
96 if value
95 if value
97 value = value.to_s.downcase
96 value = value.to_s.downcase
98 if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value
97 if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value
99 if logger
98 if logger
100 logger.info "MailHandler: ignoring email with #{key}:#{value} header"
99 logger.info "MailHandler: ignoring email with #{key}:#{value} header"
101 end
100 end
102 return false
101 return false
103 end
102 end
104 end
103 end
105 end
104 end
106 @user = User.find_by_mail(sender_email) if sender_email.present?
105 @user = User.find_by_mail(sender_email) if sender_email.present?
107 if @user && !@user.active?
106 if @user && !@user.active?
108 if logger
107 if logger
109 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]"
108 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]"
110 end
109 end
111 return false
110 return false
112 end
111 end
113 if @user.nil?
112 if @user.nil?
114 # Email was submitted by an unknown user
113 # Email was submitted by an unknown user
115 case @@handler_options[:unknown_user]
114 case @@handler_options[:unknown_user]
116 when 'accept'
115 when 'accept'
117 @user = User.anonymous
116 @user = User.anonymous
118 when 'create'
117 when 'create'
119 @user = create_user_from_email
118 @user = create_user_from_email
120 if @user
119 if @user
121 if logger
120 if logger
122 logger.info "MailHandler: [#{@user.login}] account created"
121 logger.info "MailHandler: [#{@user.login}] account created"
123 end
122 end
124 add_user_to_group(@@handler_options[:default_group])
123 add_user_to_group(@@handler_options[:default_group])
125 unless @@handler_options[:no_account_notice]
124 unless @@handler_options[:no_account_notice]
126 Mailer.account_information(@user, @user.password).deliver
125 Mailer.account_information(@user, @user.password).deliver
127 end
126 end
128 else
127 else
129 if logger
128 if logger
130 logger.error "MailHandler: could not create account for [#{sender_email}]"
129 logger.error "MailHandler: could not create account for [#{sender_email}]"
131 end
130 end
132 return false
131 return false
133 end
132 end
134 else
133 else
135 # Default behaviour, emails from unknown users are ignored
134 # Default behaviour, emails from unknown users are ignored
136 if logger
135 if logger
137 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]"
136 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]"
138 end
137 end
139 return false
138 return false
140 end
139 end
141 end
140 end
142 User.current = @user
141 User.current = @user
143 dispatch
142 dispatch
144 end
143 end
145
144
146 private
145 private
147
146
148 MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+(\.[a-f0-9]+)?@}
147 MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+(\.[a-f0-9]+)?@}
149 ISSUE_REPLY_SUBJECT_RE = %r{\[(?:[^\]]*\s+)?#(\d+)\]}
148 ISSUE_REPLY_SUBJECT_RE = %r{\[(?:[^\]]*\s+)?#(\d+)\]}
150 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
149 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
151
150
152 def dispatch
151 def dispatch
153 headers = [email.in_reply_to, email.references].flatten.compact
152 headers = [email.in_reply_to, email.references].flatten.compact
154 subject = email.subject.to_s
153 subject = email.subject.to_s
155 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
154 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
156 klass, object_id = $1, $2.to_i
155 klass, object_id = $1, $2.to_i
157 method_name = "receive_#{klass}_reply"
156 method_name = "receive_#{klass}_reply"
158 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
157 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
159 send method_name, object_id
158 send method_name, object_id
160 else
159 else
161 # ignoring it
160 # ignoring it
162 end
161 end
163 elsif m = subject.match(ISSUE_REPLY_SUBJECT_RE)
162 elsif m = subject.match(ISSUE_REPLY_SUBJECT_RE)
164 receive_issue_reply(m[1].to_i)
163 receive_issue_reply(m[1].to_i)
165 elsif m = subject.match(MESSAGE_REPLY_SUBJECT_RE)
164 elsif m = subject.match(MESSAGE_REPLY_SUBJECT_RE)
166 receive_message_reply(m[1].to_i)
165 receive_message_reply(m[1].to_i)
167 else
166 else
168 dispatch_to_default
167 dispatch_to_default
169 end
168 end
170 rescue ActiveRecord::RecordInvalid => e
169 rescue ActiveRecord::RecordInvalid => e
171 # TODO: send a email to the user
170 # TODO: send a email to the user
172 logger.error e.message if logger
171 logger.error e.message if logger
173 false
172 false
174 rescue MissingInformation => e
173 rescue MissingInformation => e
175 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
174 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
176 false
175 false
177 rescue UnauthorizedAction => e
176 rescue UnauthorizedAction => e
178 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
177 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
179 false
178 false
180 end
179 end
181
180
182 def dispatch_to_default
181 def dispatch_to_default
183 receive_issue
182 receive_issue
184 end
183 end
185
184
186 # Creates a new issue
185 # Creates a new issue
187 def receive_issue
186 def receive_issue
188 project = target_project
187 project = target_project
189 # check permission
188 # check permission
190 unless @@handler_options[:no_permission_check]
189 unless @@handler_options[:no_permission_check]
191 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
190 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
192 end
191 end
193
192
194 issue = Issue.new(:author => user, :project => project)
193 issue = Issue.new(:author => user, :project => project)
195 issue.safe_attributes = issue_attributes_from_keywords(issue)
194 issue.safe_attributes = issue_attributes_from_keywords(issue)
196 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
195 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
197 issue.subject = cleaned_up_subject
196 issue.subject = cleaned_up_subject
198 if issue.subject.blank?
197 if issue.subject.blank?
199 issue.subject = '(no subject)'
198 issue.subject = '(no subject)'
200 end
199 end
201 issue.description = cleaned_up_text_body
200 issue.description = cleaned_up_text_body
202 issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
201 issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
203
202
204 # add To and Cc as watchers before saving so the watchers can reply to Redmine
203 # add To and Cc as watchers before saving so the watchers can reply to Redmine
205 add_watchers(issue)
204 add_watchers(issue)
206 issue.save!
205 issue.save!
207 add_attachments(issue)
206 add_attachments(issue)
208 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger
207 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger
209 issue
208 issue
210 end
209 end
211
210
212 # Adds a note to an existing issue
211 # Adds a note to an existing issue
213 def receive_issue_reply(issue_id, from_journal=nil)
212 def receive_issue_reply(issue_id, from_journal=nil)
214 issue = Issue.find_by_id(issue_id)
213 issue = Issue.find_by_id(issue_id)
215 return unless issue
214 return unless issue
216 # check permission
215 # check permission
217 unless @@handler_options[:no_permission_check]
216 unless @@handler_options[:no_permission_check]
218 unless user.allowed_to?(:add_issue_notes, issue.project) ||
217 unless user.allowed_to?(:add_issue_notes, issue.project) ||
219 user.allowed_to?(:edit_issues, issue.project)
218 user.allowed_to?(:edit_issues, issue.project)
220 raise UnauthorizedAction
219 raise UnauthorizedAction
221 end
220 end
222 end
221 end
223
222
224 # ignore CLI-supplied defaults for new issues
223 # ignore CLI-supplied defaults for new issues
225 @@handler_options[:issue].clear
224 @@handler_options[:issue].clear
226
225
227 journal = issue.init_journal(user)
226 journal = issue.init_journal(user)
228 if from_journal && from_journal.private_notes?
227 if from_journal && from_journal.private_notes?
229 # If the received email was a reply to a private note, make the added note private
228 # If the received email was a reply to a private note, make the added note private
230 issue.private_notes = true
229 issue.private_notes = true
231 end
230 end
232 issue.safe_attributes = issue_attributes_from_keywords(issue)
231 issue.safe_attributes = issue_attributes_from_keywords(issue)
233 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
232 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
234 journal.notes = cleaned_up_text_body
233 journal.notes = cleaned_up_text_body
235 add_attachments(issue)
234 add_attachments(issue)
236 issue.save!
235 issue.save!
237 if logger
236 if logger
238 logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
237 logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
239 end
238 end
240 journal
239 journal
241 end
240 end
242
241
243 # Reply will be added to the issue
242 # Reply will be added to the issue
244 def receive_journal_reply(journal_id)
243 def receive_journal_reply(journal_id)
245 journal = Journal.find_by_id(journal_id)
244 journal = Journal.find_by_id(journal_id)
246 if journal && journal.journalized_type == 'Issue'
245 if journal && journal.journalized_type == 'Issue'
247 receive_issue_reply(journal.journalized_id, journal)
246 receive_issue_reply(journal.journalized_id, journal)
248 end
247 end
249 end
248 end
250
249
251 # Receives a reply to a forum message
250 # Receives a reply to a forum message
252 def receive_message_reply(message_id)
251 def receive_message_reply(message_id)
253 message = Message.find_by_id(message_id)
252 message = Message.find_by_id(message_id)
254 if message
253 if message
255 message = message.root
254 message = message.root
256
255
257 unless @@handler_options[:no_permission_check]
256 unless @@handler_options[:no_permission_check]
258 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
257 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
259 end
258 end
260
259
261 if !message.locked?
260 if !message.locked?
262 reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip,
261 reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip,
263 :content => cleaned_up_text_body)
262 :content => cleaned_up_text_body)
264 reply.author = user
263 reply.author = user
265 reply.board = message.board
264 reply.board = message.board
266 message.children << reply
265 message.children << reply
267 add_attachments(reply)
266 add_attachments(reply)
268 reply
267 reply
269 else
268 else
270 if logger
269 if logger
271 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
270 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
272 end
271 end
273 end
272 end
274 end
273 end
275 end
274 end
276
275
277 def add_attachments(obj)
276 def add_attachments(obj)
278 if email.attachments && email.attachments.any?
277 if email.attachments && email.attachments.any?
279 email.attachments.each do |attachment|
278 email.attachments.each do |attachment|
280 next unless accept_attachment?(attachment)
279 next unless accept_attachment?(attachment)
281 obj.attachments << Attachment.create(:container => obj,
280 obj.attachments << Attachment.create(:container => obj,
282 :file => attachment.decoded,
281 :file => attachment.decoded,
283 :filename => attachment.filename,
282 :filename => attachment.filename,
284 :author => user,
283 :author => user,
285 :content_type => attachment.mime_type)
284 :content_type => attachment.mime_type)
286 end
285 end
287 end
286 end
288 end
287 end
289
288
290 # Returns false if the +attachment+ of the incoming email should be ignored
289 # Returns false if the +attachment+ of the incoming email should be ignored
291 def accept_attachment?(attachment)
290 def accept_attachment?(attachment)
292 @excluded ||= Setting.mail_handler_excluded_filenames.to_s.split(',').map(&:strip).reject(&:blank?)
291 @excluded ||= Setting.mail_handler_excluded_filenames.to_s.split(',').map(&:strip).reject(&:blank?)
293 @excluded.each do |pattern|
292 @excluded.each do |pattern|
294 regexp = %r{\A#{Regexp.escape(pattern).gsub("\\*", ".*")}\z}i
293 regexp = %r{\A#{Regexp.escape(pattern).gsub("\\*", ".*")}\z}i
295 if attachment.filename.to_s =~ regexp
294 if attachment.filename.to_s =~ regexp
296 logger.info "MailHandler: ignoring attachment #{attachment.filename} matching #{pattern}"
295 logger.info "MailHandler: ignoring attachment #{attachment.filename} matching #{pattern}"
297 return false
296 return false
298 end
297 end
299 end
298 end
300 true
299 true
301 end
300 end
302
301
303 # Adds To and Cc as watchers of the given object if the sender has the
302 # Adds To and Cc as watchers of the given object if the sender has the
304 # appropriate permission
303 # appropriate permission
305 def add_watchers(obj)
304 def add_watchers(obj)
306 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
305 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
307 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
306 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
308 unless addresses.empty?
307 unless addresses.empty?
309 User.active.having_mail(addresses).each do |w|
308 User.active.having_mail(addresses).each do |w|
310 obj.add_watcher(w)
309 obj.add_watcher(w)
311 end
310 end
312 end
311 end
313 end
312 end
314 end
313 end
315
314
316 def get_keyword(attr, options={})
315 def get_keyword(attr, options={})
317 @keywords ||= {}
316 @keywords ||= {}
318 if @keywords.has_key?(attr)
317 if @keywords.has_key?(attr)
319 @keywords[attr]
318 @keywords[attr]
320 else
319 else
321 @keywords[attr] = begin
320 @keywords[attr] = begin
322 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) &&
321 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) &&
323 (v = extract_keyword!(cleaned_up_text_body, attr, options[:format]))
322 (v = extract_keyword!(cleaned_up_text_body, attr, options[:format]))
324 v
323 v
325 elsif !@@handler_options[:issue][attr].blank?
324 elsif !@@handler_options[:issue][attr].blank?
326 @@handler_options[:issue][attr]
325 @@handler_options[:issue][attr]
327 end
326 end
328 end
327 end
329 end
328 end
330 end
329 end
331
330
332 # Destructively extracts the value for +attr+ in +text+
331 # Destructively extracts the value for +attr+ in +text+
333 # Returns nil if no matching keyword found
332 # Returns nil if no matching keyword found
334 def extract_keyword!(text, attr, format=nil)
333 def extract_keyword!(text, attr, format=nil)
335 keys = [attr.to_s.humanize]
334 keys = [attr.to_s.humanize]
336 if attr.is_a?(Symbol)
335 if attr.is_a?(Symbol)
337 if user && user.language.present?
336 if user && user.language.present?
338 keys << l("field_#{attr}", :default => '', :locale => user.language)
337 keys << l("field_#{attr}", :default => '', :locale => user.language)
339 end
338 end
340 if Setting.default_language.present?
339 if Setting.default_language.present?
341 keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
340 keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
342 end
341 end
343 end
342 end
344 keys.reject! {|k| k.blank?}
343 keys.reject! {|k| k.blank?}
345 keys.collect! {|k| Regexp.escape(k)}
344 keys.collect! {|k| Regexp.escape(k)}
346 format ||= '.+'
345 format ||= '.+'
347 keyword = nil
346 keyword = nil
348 regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i
347 regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i
349 if m = text.match(regexp)
348 if m = text.match(regexp)
350 keyword = m[2].strip
349 keyword = m[2].strip
351 text.sub!(regexp, '')
350 text.sub!(regexp, '')
352 end
351 end
353 keyword
352 keyword
354 end
353 end
355
354
356 def target_project
355 def target_project
357 # TODO: other ways to specify project:
356 # TODO: other ways to specify project:
358 # * parse the email To field
357 # * parse the email To field
359 # * specific project (eg. Setting.mail_handler_target_project)
358 # * specific project (eg. Setting.mail_handler_target_project)
360 target = Project.find_by_identifier(get_keyword(:project))
359 target = Project.find_by_identifier(get_keyword(:project))
361 if target.nil?
360 if target.nil?
362 # Invalid project keyword, use the project specified as the default one
361 # Invalid project keyword, use the project specified as the default one
363 default_project = @@handler_options[:issue][:project]
362 default_project = @@handler_options[:issue][:project]
364 if default_project.present?
363 if default_project.present?
365 target = Project.find_by_identifier(default_project)
364 target = Project.find_by_identifier(default_project)
366 end
365 end
367 end
366 end
368 raise MissingInformation.new('Unable to determine target project') if target.nil?
367 raise MissingInformation.new('Unable to determine target project') if target.nil?
369 target
368 target
370 end
369 end
371
370
372 # Returns a Hash of issue attributes extracted from keywords in the email body
371 # Returns a Hash of issue attributes extracted from keywords in the email body
373 def issue_attributes_from_keywords(issue)
372 def issue_attributes_from_keywords(issue)
374 assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue)
373 assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue)
375
374
376 attrs = {
375 attrs = {
377 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
376 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
378 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),
377 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),
379 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
378 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
380 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
379 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
381 'assigned_to_id' => assigned_to.try(:id),
380 'assigned_to_id' => assigned_to.try(:id),
382 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) &&
381 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) &&
383 issue.project.shared_versions.named(k).first.try(:id),
382 issue.project.shared_versions.named(k).first.try(:id),
384 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
383 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
385 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
384 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
386 'estimated_hours' => get_keyword(:estimated_hours, :override => true),
385 'estimated_hours' => get_keyword(:estimated_hours, :override => true),
387 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0')
386 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0')
388 }.delete_if {|k, v| v.blank? }
387 }.delete_if {|k, v| v.blank? }
389
388
390 if issue.new_record? && attrs['tracker_id'].nil?
389 if issue.new_record? && attrs['tracker_id'].nil?
391 attrs['tracker_id'] = issue.project.trackers.first.try(:id)
390 attrs['tracker_id'] = issue.project.trackers.first.try(:id)
392 end
391 end
393
392
394 attrs
393 attrs
395 end
394 end
396
395
397 # Returns a Hash of issue custom field values extracted from keywords in the email body
396 # Returns a Hash of issue custom field values extracted from keywords in the email body
398 def custom_field_values_from_keywords(customized)
397 def custom_field_values_from_keywords(customized)
399 customized.custom_field_values.inject({}) do |h, v|
398 customized.custom_field_values.inject({}) do |h, v|
400 if keyword = get_keyword(v.custom_field.name, :override => true)
399 if keyword = get_keyword(v.custom_field.name, :override => true)
401 h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized)
400 h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized)
402 end
401 end
403 h
402 h
404 end
403 end
405 end
404 end
406
405
407 # Returns the text/plain part of the email
406 # Returns the text/plain part of the email
408 # If not found (eg. HTML-only email), returns the body with tags removed
407 # If not found (eg. HTML-only email), returns the body with tags removed
409 def plain_text_body
408 def plain_text_body
410 return @plain_text_body unless @plain_text_body.nil?
409 return @plain_text_body unless @plain_text_body.nil?
411
410
412 parts = if (text_parts = email.all_parts.select {|p| p.mime_type == 'text/plain'}).present?
411 parts = if (text_parts = email.all_parts.select {|p| p.mime_type == 'text/plain'}).present?
413 text_parts
412 text_parts
414 elsif (html_parts = email.all_parts.select {|p| p.mime_type == 'text/html'}).present?
413 elsif (html_parts = email.all_parts.select {|p| p.mime_type == 'text/html'}).present?
415 html_parts
414 html_parts
416 else
415 else
417 [email]
416 [email]
418 end
417 end
419
418
420 parts.reject! do |part|
419 parts.reject! do |part|
421 part.attachment?
420 part.attachment?
422 end
421 end
423
422
424 @plain_text_body = parts.map do |p|
423 @plain_text_body = parts.map do |p|
425 body_charset = Mail::RubyVer.respond_to?(:pick_encoding) ?
424 body_charset = Mail::RubyVer.respond_to?(:pick_encoding) ?
426 Mail::RubyVer.pick_encoding(p.charset).to_s : p.charset
425 Mail::RubyVer.pick_encoding(p.charset).to_s : p.charset
427 Redmine::CodesetUtil.to_utf8(p.body.decoded, body_charset)
426 Redmine::CodesetUtil.to_utf8(p.body.decoded, body_charset)
428 end.join("\r\n")
427 end.join("\r\n")
429
428
430 # strip html tags and remove doctype directive
429 # strip html tags and remove doctype directive
431 if parts.any? {|p| p.mime_type == 'text/html'}
430 if parts.any? {|p| p.mime_type == 'text/html'}
432 @plain_text_body = strip_tags(@plain_text_body.strip)
431 @plain_text_body = strip_tags(@plain_text_body.strip)
433 @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
432 @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
434 end
433 end
435
434
436 @plain_text_body
435 @plain_text_body
437 end
436 end
438
437
439 def cleaned_up_text_body
438 def cleaned_up_text_body
440 @cleaned_up_text_body ||= cleanup_body(plain_text_body)
439 @cleaned_up_text_body ||= cleanup_body(plain_text_body)
441 end
440 end
442
441
443 def cleaned_up_subject
442 def cleaned_up_subject
444 subject = email.subject.to_s
443 subject = email.subject.to_s
445 subject.strip[0,255]
444 subject.strip[0,255]
446 end
445 end
447
446
448 def self.full_sanitizer
447 def self.full_sanitizer
449 @full_sanitizer ||= HTML::FullSanitizer.new
448 @full_sanitizer ||= HTML::FullSanitizer.new
450 end
449 end
451
450
452 def self.assign_string_attribute_with_limit(object, attribute, value, limit=nil)
451 def self.assign_string_attribute_with_limit(object, attribute, value, limit=nil)
453 limit ||= object.class.columns_hash[attribute.to_s].limit || 255
452 limit ||= object.class.columns_hash[attribute.to_s].limit || 255
454 value = value.to_s.slice(0, limit)
453 value = value.to_s.slice(0, limit)
455 object.send("#{attribute}=", value)
454 object.send("#{attribute}=", value)
456 end
455 end
457
456
458 # Returns a User from an email address and a full name
457 # Returns a User from an email address and a full name
459 def self.new_user_from_attributes(email_address, fullname=nil)
458 def self.new_user_from_attributes(email_address, fullname=nil)
460 user = User.new
459 user = User.new
461
460
462 # Truncating the email address would result in an invalid format
461 # Truncating the email address would result in an invalid format
463 user.mail = email_address
462 user.mail = email_address
464 assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
463 assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
465
464
466 names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
465 names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
467 assign_string_attribute_with_limit(user, 'firstname', names.shift, 30)
466 assign_string_attribute_with_limit(user, 'firstname', names.shift, 30)
468 assign_string_attribute_with_limit(user, 'lastname', names.join(' '), 30)
467 assign_string_attribute_with_limit(user, 'lastname', names.join(' '), 30)
469 user.lastname = '-' if user.lastname.blank?
468 user.lastname = '-' if user.lastname.blank?
470 user.language = Setting.default_language
469 user.language = Setting.default_language
471 user.generate_password = true
470 user.generate_password = true
472 user.mail_notification = 'only_my_events'
471 user.mail_notification = 'only_my_events'
473
472
474 unless user.valid?
473 unless user.valid?
475 user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
474 user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
476 user.firstname = "-" unless user.errors[:firstname].blank?
475 user.firstname = "-" unless user.errors[:firstname].blank?
477 (puts user.errors[:lastname];user.lastname = "-") unless user.errors[:lastname].blank?
476 (puts user.errors[:lastname];user.lastname = "-") unless user.errors[:lastname].blank?
478 end
477 end
479
478
480 user
479 user
481 end
480 end
482
481
483 # Creates a User for the +email+ sender
482 # Creates a User for the +email+ sender
484 # Returns the user or nil if it could not be created
483 # Returns the user or nil if it could not be created
485 def create_user_from_email
484 def create_user_from_email
486 from = email.header['from'].to_s
485 from = email.header['from'].to_s
487 addr, name = from, nil
486 addr, name = from, nil
488 if m = from.match(/^"?(.+?)"?\s+<(.+@.+)>$/)
487 if m = from.match(/^"?(.+?)"?\s+<(.+@.+)>$/)
489 addr, name = m[2], m[1]
488 addr, name = m[2], m[1]
490 end
489 end
491 if addr.present?
490 if addr.present?
492 user = self.class.new_user_from_attributes(addr, name)
491 user = self.class.new_user_from_attributes(addr, name)
493 if @@handler_options[:no_notification]
492 if @@handler_options[:no_notification]
494 user.mail_notification = 'none'
493 user.mail_notification = 'none'
495 end
494 end
496 if user.save
495 if user.save
497 user
496 user
498 else
497 else
499 logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger
498 logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger
500 nil
499 nil
501 end
500 end
502 else
501 else
503 logger.error "MailHandler: failed to create User: no FROM address found" if logger
502 logger.error "MailHandler: failed to create User: no FROM address found" if logger
504 nil
503 nil
505 end
504 end
506 end
505 end
507
506
508 # Adds the newly created user to default group
507 # Adds the newly created user to default group
509 def add_user_to_group(default_group)
508 def add_user_to_group(default_group)
510 if default_group.present?
509 if default_group.present?
511 default_group.split(',').each do |group_name|
510 default_group.split(',').each do |group_name|
512 if group = Group.named(group_name).first
511 if group = Group.named(group_name).first
513 group.users << @user
512 group.users << @user
514 elsif logger
513 elsif logger
515 logger.warn "MailHandler: could not add user to [#{group_name}], group not found"
514 logger.warn "MailHandler: could not add user to [#{group_name}], group not found"
516 end
515 end
517 end
516 end
518 end
517 end
519 end
518 end
520
519
521 # Removes the email body of text after the truncation configurations.
520 # Removes the email body of text after the truncation configurations.
522 def cleanup_body(body)
521 def cleanup_body(body)
523 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
522 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
524 unless delimiters.empty?
523 unless delimiters.empty?
525 regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
524 regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
526 body = body.gsub(regex, '')
525 body = body.gsub(regex, '')
527 end
526 end
528 body.strip
527 body.strip
529 end
528 end
530
529
531 def find_assignee_from_keyword(keyword, issue)
530 def find_assignee_from_keyword(keyword, issue)
532 keyword = keyword.to_s.downcase
531 keyword = keyword.to_s.downcase
533 assignable = issue.assignable_users
532 assignable = issue.assignable_users
534 assignee = nil
533 assignee = nil
535 assignee ||= assignable.detect {|a|
534 assignee ||= assignable.detect {|a|
536 a.mail.to_s.downcase == keyword ||
535 a.mail.to_s.downcase == keyword ||
537 a.login.to_s.downcase == keyword
536 a.login.to_s.downcase == keyword
538 }
537 }
539 if assignee.nil? && keyword.match(/ /)
538 if assignee.nil? && keyword.match(/ /)
540 firstname, lastname = *(keyword.split) # "First Last Throwaway"
539 firstname, lastname = *(keyword.split) # "First Last Throwaway"
541 assignee ||= assignable.detect {|a|
540 assignee ||= assignable.detect {|a|
542 a.is_a?(User) && a.firstname.to_s.downcase == firstname &&
541 a.is_a?(User) && a.firstname.to_s.downcase == firstname &&
543 a.lastname.to_s.downcase == lastname
542 a.lastname.to_s.downcase == lastname
544 }
543 }
545 end
544 end
546 if assignee.nil?
545 if assignee.nil?
547 assignee ||= assignable.detect {|a| a.name.downcase == keyword}
546 assignee ||= assignable.detect {|a| a.name.downcase == keyword}
548 end
547 end
549 assignee
548 assignee
550 end
549 end
551 end
550 end
@@ -1,538 +1,538
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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 Redmine::I18n
24 include Redmine::I18n
25
25
26 def self.default_url_options
26 def self.default_url_options
27 options = {:protocol => Setting.protocol}
27 options = {:protocol => Setting.protocol}
28 if Setting.host_name.to_s =~ /\A(https?\:\/\/)?(.+?)(\:(\d+))?(\/.+)?\z/i
28 if Setting.host_name.to_s =~ /\A(https?\:\/\/)?(.+?)(\:(\d+))?(\/.+)?\z/i
29 host, port, prefix = $2, $4, $5
29 host, port, prefix = $2, $4, $5
30 options.merge!({
30 options.merge!({
31 :host => host, :port => port, :script_name => prefix
31 :host => host, :port => port, :script_name => prefix
32 })
32 })
33 else
33 else
34 options[:host] = Setting.host_name
34 options[:host] = Setting.host_name
35 end
35 end
36 options
36 options
37 end
37 end
38
38
39 # Builds a mail for notifying to_users and cc_users about a new issue
39 # Builds a mail for notifying to_users and cc_users about a new issue
40 def issue_add(issue, to_users, cc_users)
40 def issue_add(issue, to_users, cc_users)
41 redmine_headers 'Project' => issue.project.identifier,
41 redmine_headers 'Project' => issue.project.identifier,
42 'Issue-Id' => issue.id,
42 'Issue-Id' => issue.id,
43 'Issue-Author' => issue.author.login
43 'Issue-Author' => issue.author.login
44 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
44 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
45 message_id issue
45 message_id issue
46 references issue
46 references issue
47 @author = issue.author
47 @author = issue.author
48 @issue = issue
48 @issue = issue
49 @users = to_users + cc_users
49 @users = to_users + cc_users
50 @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue)
50 @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue)
51 mail :to => to_users,
51 mail :to => to_users,
52 :cc => cc_users,
52 :cc => cc_users,
53 :subject => "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
53 :subject => "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
54 end
54 end
55
55
56 # Notifies users about a new issue
56 # Notifies users about a new issue
57 def self.deliver_issue_add(issue)
57 def self.deliver_issue_add(issue)
58 to = issue.notified_users
58 to = issue.notified_users
59 cc = issue.notified_watchers - to
59 cc = issue.notified_watchers - to
60 issue.each_notification(to + cc) do |users|
60 issue.each_notification(to + cc) do |users|
61 Mailer.issue_add(issue, to & users, cc & users).deliver
61 Mailer.issue_add(issue, to & users, cc & users).deliver
62 end
62 end
63 end
63 end
64
64
65 # Builds a mail for notifying to_users and cc_users about an issue update
65 # Builds a mail for notifying to_users and cc_users about an issue update
66 def issue_edit(journal, to_users, cc_users)
66 def issue_edit(journal, to_users, cc_users)
67 issue = journal.journalized
67 issue = journal.journalized
68 redmine_headers 'Project' => issue.project.identifier,
68 redmine_headers 'Project' => issue.project.identifier,
69 'Issue-Id' => issue.id,
69 'Issue-Id' => issue.id,
70 'Issue-Author' => issue.author.login
70 'Issue-Author' => issue.author.login
71 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
71 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
72 message_id journal
72 message_id journal
73 references issue
73 references issue
74 @author = journal.user
74 @author = journal.user
75 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
75 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
76 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
76 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
77 s << issue.subject
77 s << issue.subject
78 @issue = issue
78 @issue = issue
79 @users = to_users + cc_users
79 @users = to_users + cc_users
80 @journal = journal
80 @journal = journal
81 @journal_details = journal.visible_details(@users.first)
81 @journal_details = journal.visible_details(@users.first)
82 @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}")
82 @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}")
83 mail :to => to_users,
83 mail :to => to_users,
84 :cc => cc_users,
84 :cc => cc_users,
85 :subject => s
85 :subject => s
86 end
86 end
87
87
88 # Notifies users about an issue update
88 # Notifies users about an issue update
89 def self.deliver_issue_edit(journal)
89 def self.deliver_issue_edit(journal)
90 issue = journal.journalized.reload
90 issue = journal.journalized.reload
91 to = journal.notified_users
91 to = journal.notified_users
92 cc = journal.notified_watchers - to
92 cc = journal.notified_watchers - to
93 journal.each_notification(to + cc) do |users|
93 journal.each_notification(to + cc) do |users|
94 issue.each_notification(users) do |users2|
94 issue.each_notification(users) do |users2|
95 Mailer.issue_edit(journal, to & users2, cc & users2).deliver
95 Mailer.issue_edit(journal, to & users2, cc & users2).deliver
96 end
96 end
97 end
97 end
98 end
98 end
99
99
100 def reminder(user, issues, days)
100 def reminder(user, issues, days)
101 set_language_if_valid user.language
101 set_language_if_valid user.language
102 @issues = issues
102 @issues = issues
103 @days = days
103 @days = days
104 @issues_url = url_for(:controller => 'issues', :action => 'index',
104 @issues_url = url_for(:controller => 'issues', :action => 'index',
105 :set_filter => 1, :assigned_to_id => user.id,
105 :set_filter => 1, :assigned_to_id => user.id,
106 :sort => 'due_date:asc')
106 :sort => 'due_date:asc')
107 mail :to => user,
107 mail :to => user,
108 :subject => l(:mail_subject_reminder, :count => issues.size, :days => days)
108 :subject => l(:mail_subject_reminder, :count => issues.size, :days => days)
109 end
109 end
110
110
111 # Builds a Mail::Message object used to email users belonging to the added document's project.
111 # Builds a Mail::Message object used to email users belonging to the added document's project.
112 #
112 #
113 # Example:
113 # Example:
114 # document_added(document) => Mail::Message object
114 # document_added(document) => Mail::Message object
115 # Mailer.document_added(document).deliver => sends an email to the document's project recipients
115 # Mailer.document_added(document).deliver => sends an email to the document's project recipients
116 def document_added(document)
116 def document_added(document)
117 redmine_headers 'Project' => document.project.identifier
117 redmine_headers 'Project' => document.project.identifier
118 @author = User.current
118 @author = User.current
119 @document = document
119 @document = document
120 @document_url = url_for(:controller => 'documents', :action => 'show', :id => document)
120 @document_url = url_for(:controller => 'documents', :action => 'show', :id => document)
121 mail :to => document.notified_users,
121 mail :to => document.notified_users,
122 :subject => "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
122 :subject => "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
123 end
123 end
124
124
125 # Builds a Mail::Message object used to email recipients of a project when an attachements are added.
125 # Builds a Mail::Message object used to email recipients of a project when an attachements are added.
126 #
126 #
127 # Example:
127 # Example:
128 # attachments_added(attachments) => Mail::Message object
128 # attachments_added(attachments) => Mail::Message object
129 # Mailer.attachments_added(attachments).deliver => sends an email to the project's recipients
129 # Mailer.attachments_added(attachments).deliver => sends an email to the project's recipients
130 def attachments_added(attachments)
130 def attachments_added(attachments)
131 container = attachments.first.container
131 container = attachments.first.container
132 added_to = ''
132 added_to = ''
133 added_to_url = ''
133 added_to_url = ''
134 @author = attachments.first.author
134 @author = attachments.first.author
135 case container.class.name
135 case container.class.name
136 when 'Project'
136 when 'Project'
137 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container)
137 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container)
138 added_to = "#{l(:label_project)}: #{container}"
138 added_to = "#{l(:label_project)}: #{container}"
139 recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
139 recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
140 when 'Version'
140 when 'Version'
141 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project)
141 added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project)
142 added_to = "#{l(:label_version)}: #{container.name}"
142 added_to = "#{l(:label_version)}: #{container.name}"
143 recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
143 recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
144 when 'Document'
144 when 'Document'
145 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
145 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
146 added_to = "#{l(:label_document)}: #{container.title}"
146 added_to = "#{l(:label_document)}: #{container.title}"
147 recipients = container.notified_users
147 recipients = container.notified_users
148 end
148 end
149 redmine_headers 'Project' => container.project.identifier
149 redmine_headers 'Project' => container.project.identifier
150 @attachments = attachments
150 @attachments = attachments
151 @added_to = added_to
151 @added_to = added_to
152 @added_to_url = added_to_url
152 @added_to_url = added_to_url
153 mail :to => recipients,
153 mail :to => recipients,
154 :subject => "[#{container.project.name}] #{l(:label_attachment_new)}"
154 :subject => "[#{container.project.name}] #{l(:label_attachment_new)}"
155 end
155 end
156
156
157 # Builds a Mail::Message object used to email recipients of a news' project when a news item is added.
157 # Builds a Mail::Message object used to email recipients of a news' project when a news item is added.
158 #
158 #
159 # Example:
159 # Example:
160 # news_added(news) => Mail::Message object
160 # news_added(news) => Mail::Message object
161 # Mailer.news_added(news).deliver => sends an email to the news' project recipients
161 # Mailer.news_added(news).deliver => sends an email to the news' project recipients
162 def news_added(news)
162 def news_added(news)
163 redmine_headers 'Project' => news.project.identifier
163 redmine_headers 'Project' => news.project.identifier
164 @author = news.author
164 @author = news.author
165 message_id news
165 message_id news
166 references news
166 references news
167 @news = news
167 @news = news
168 @news_url = url_for(:controller => 'news', :action => 'show', :id => news)
168 @news_url = url_for(:controller => 'news', :action => 'show', :id => news)
169 mail :to => news.notified_users,
169 mail :to => news.notified_users,
170 :cc => news.notified_watchers_for_added_news,
170 :cc => news.notified_watchers_for_added_news,
171 :subject => "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
171 :subject => "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
172 end
172 end
173
173
174 # Builds a Mail::Message object used to email recipients of a news' project when a news comment is added.
174 # Builds a Mail::Message object used to email recipients of a news' project when a news comment is added.
175 #
175 #
176 # Example:
176 # Example:
177 # news_comment_added(comment) => Mail::Message object
177 # news_comment_added(comment) => Mail::Message object
178 # Mailer.news_comment_added(comment) => sends an email to the news' project recipients
178 # Mailer.news_comment_added(comment) => sends an email to the news' project recipients
179 def news_comment_added(comment)
179 def news_comment_added(comment)
180 news = comment.commented
180 news = comment.commented
181 redmine_headers 'Project' => news.project.identifier
181 redmine_headers 'Project' => news.project.identifier
182 @author = comment.author
182 @author = comment.author
183 message_id comment
183 message_id comment
184 references news
184 references news
185 @news = news
185 @news = news
186 @comment = comment
186 @comment = comment
187 @news_url = url_for(:controller => 'news', :action => 'show', :id => news)
187 @news_url = url_for(:controller => 'news', :action => 'show', :id => news)
188 mail :to => news.notified_users,
188 mail :to => news.notified_users,
189 :cc => news.notified_watchers,
189 :cc => news.notified_watchers,
190 :subject => "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}"
190 :subject => "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}"
191 end
191 end
192
192
193 # Builds a Mail::Message object used to email the recipients of the specified message that was posted.
193 # Builds a Mail::Message object used to email the recipients of the specified message that was posted.
194 #
194 #
195 # Example:
195 # Example:
196 # message_posted(message) => Mail::Message object
196 # message_posted(message) => Mail::Message object
197 # Mailer.message_posted(message).deliver => sends an email to the recipients
197 # Mailer.message_posted(message).deliver => sends an email to the recipients
198 def message_posted(message)
198 def message_posted(message)
199 redmine_headers 'Project' => message.project.identifier,
199 redmine_headers 'Project' => message.project.identifier,
200 'Topic-Id' => (message.parent_id || message.id)
200 'Topic-Id' => (message.parent_id || message.id)
201 @author = message.author
201 @author = message.author
202 message_id message
202 message_id message
203 references message.root
203 references message.root
204 recipients = message.notified_users
204 recipients = message.notified_users
205 cc = ((message.root.notified_watchers + message.board.notified_watchers).uniq - recipients)
205 cc = ((message.root.notified_watchers + message.board.notified_watchers).uniq - recipients)
206 @message = message
206 @message = message
207 @message_url = url_for(message.event_url)
207 @message_url = url_for(message.event_url)
208 mail :to => recipients,
208 mail :to => recipients,
209 :cc => cc,
209 :cc => cc,
210 :subject => "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
210 :subject => "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
211 end
211 end
212
212
213 # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was added.
213 # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was added.
214 #
214 #
215 # Example:
215 # Example:
216 # wiki_content_added(wiki_content) => Mail::Message object
216 # wiki_content_added(wiki_content) => Mail::Message object
217 # Mailer.wiki_content_added(wiki_content).deliver => sends an email to the project's recipients
217 # Mailer.wiki_content_added(wiki_content).deliver => sends an email to the project's recipients
218 def wiki_content_added(wiki_content)
218 def wiki_content_added(wiki_content)
219 redmine_headers 'Project' => wiki_content.project.identifier,
219 redmine_headers 'Project' => wiki_content.project.identifier,
220 'Wiki-Page-Id' => wiki_content.page.id
220 'Wiki-Page-Id' => wiki_content.page.id
221 @author = wiki_content.author
221 @author = wiki_content.author
222 message_id wiki_content
222 message_id wiki_content
223 recipients = wiki_content.notified_users
223 recipients = wiki_content.notified_users
224 cc = wiki_content.page.wiki.notified_watchers - recipients
224 cc = wiki_content.page.wiki.notified_watchers - recipients
225 @wiki_content = wiki_content
225 @wiki_content = wiki_content
226 @wiki_content_url = url_for(:controller => 'wiki', :action => 'show',
226 @wiki_content_url = url_for(:controller => 'wiki', :action => 'show',
227 :project_id => wiki_content.project,
227 :project_id => wiki_content.project,
228 :id => wiki_content.page.title)
228 :id => wiki_content.page.title)
229 mail :to => recipients,
229 mail :to => recipients,
230 :cc => cc,
230 :cc => cc,
231 :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :id => wiki_content.page.pretty_title)}"
231 :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :id => wiki_content.page.pretty_title)}"
232 end
232 end
233
233
234 # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was updated.
234 # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was updated.
235 #
235 #
236 # Example:
236 # Example:
237 # wiki_content_updated(wiki_content) => Mail::Message object
237 # wiki_content_updated(wiki_content) => Mail::Message object
238 # Mailer.wiki_content_updated(wiki_content).deliver => sends an email to the project's recipients
238 # Mailer.wiki_content_updated(wiki_content).deliver => sends an email to the project's recipients
239 def wiki_content_updated(wiki_content)
239 def wiki_content_updated(wiki_content)
240 redmine_headers 'Project' => wiki_content.project.identifier,
240 redmine_headers 'Project' => wiki_content.project.identifier,
241 'Wiki-Page-Id' => wiki_content.page.id
241 'Wiki-Page-Id' => wiki_content.page.id
242 @author = wiki_content.author
242 @author = wiki_content.author
243 message_id wiki_content
243 message_id wiki_content
244 recipients = wiki_content.notified_users
244 recipients = wiki_content.notified_users
245 cc = wiki_content.page.wiki.notified_watchers + wiki_content.page.notified_watchers - recipients
245 cc = wiki_content.page.wiki.notified_watchers + wiki_content.page.notified_watchers - recipients
246 @wiki_content = wiki_content
246 @wiki_content = wiki_content
247 @wiki_content_url = url_for(:controller => 'wiki', :action => 'show',
247 @wiki_content_url = url_for(:controller => 'wiki', :action => 'show',
248 :project_id => wiki_content.project,
248 :project_id => wiki_content.project,
249 :id => wiki_content.page.title)
249 :id => wiki_content.page.title)
250 @wiki_diff_url = url_for(:controller => 'wiki', :action => 'diff',
250 @wiki_diff_url = url_for(:controller => 'wiki', :action => 'diff',
251 :project_id => wiki_content.project, :id => wiki_content.page.title,
251 :project_id => wiki_content.project, :id => wiki_content.page.title,
252 :version => wiki_content.version)
252 :version => wiki_content.version)
253 mail :to => recipients,
253 mail :to => recipients,
254 :cc => cc,
254 :cc => cc,
255 :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :id => wiki_content.page.pretty_title)}"
255 :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :id => wiki_content.page.pretty_title)}"
256 end
256 end
257
257
258 # Builds a Mail::Message object used to email the specified user their account information.
258 # Builds a Mail::Message object used to email the specified user their account information.
259 #
259 #
260 # Example:
260 # Example:
261 # account_information(user, password) => Mail::Message object
261 # account_information(user, password) => Mail::Message object
262 # Mailer.account_information(user, password).deliver => sends account information to the user
262 # Mailer.account_information(user, password).deliver => sends account information to the user
263 def account_information(user, password)
263 def account_information(user, password)
264 set_language_if_valid user.language
264 set_language_if_valid user.language
265 @user = user
265 @user = user
266 @password = password
266 @password = password
267 @login_url = url_for(:controller => 'account', :action => 'login')
267 @login_url = url_for(:controller => 'account', :action => 'login')
268 mail :to => user.mail,
268 mail :to => user.mail,
269 :subject => l(:mail_subject_register, Setting.app_title)
269 :subject => l(:mail_subject_register, Setting.app_title)
270 end
270 end
271
271
272 # Builds a Mail::Message object used to email all active administrators of an account activation request.
272 # Builds a Mail::Message object used to email all active administrators of an account activation request.
273 #
273 #
274 # Example:
274 # Example:
275 # account_activation_request(user) => Mail::Message object
275 # account_activation_request(user) => Mail::Message object
276 # Mailer.account_activation_request(user).deliver => sends an email to all active administrators
276 # Mailer.account_activation_request(user).deliver => sends an email to all active administrators
277 def account_activation_request(user)
277 def account_activation_request(user)
278 # Send the email to all active administrators
278 # Send the email to all active administrators
279 recipients = User.active.where(:admin => true)
279 recipients = User.active.where(:admin => true)
280 @user = user
280 @user = user
281 @url = url_for(:controller => 'users', :action => 'index',
281 @url = url_for(:controller => 'users', :action => 'index',
282 :status => User::STATUS_REGISTERED,
282 :status => User::STATUS_REGISTERED,
283 :sort_key => 'created_on', :sort_order => 'desc')
283 :sort_key => 'created_on', :sort_order => 'desc')
284 mail :to => recipients,
284 mail :to => recipients,
285 :subject => l(:mail_subject_account_activation_request, Setting.app_title)
285 :subject => l(:mail_subject_account_activation_request, Setting.app_title)
286 end
286 end
287
287
288 # Builds a Mail::Message object used to email the specified user that their account was activated by an administrator.
288 # Builds a Mail::Message object used to email the specified user that their account was activated by an administrator.
289 #
289 #
290 # Example:
290 # Example:
291 # account_activated(user) => Mail::Message object
291 # account_activated(user) => Mail::Message object
292 # Mailer.account_activated(user).deliver => sends an email to the registered user
292 # Mailer.account_activated(user).deliver => sends an email to the registered user
293 def account_activated(user)
293 def account_activated(user)
294 set_language_if_valid user.language
294 set_language_if_valid user.language
295 @user = user
295 @user = user
296 @login_url = url_for(:controller => 'account', :action => 'login')
296 @login_url = url_for(:controller => 'account', :action => 'login')
297 mail :to => user.mail,
297 mail :to => user.mail,
298 :subject => l(:mail_subject_register, Setting.app_title)
298 :subject => l(:mail_subject_register, Setting.app_title)
299 end
299 end
300
300
301 def lost_password(token, recipient=nil)
301 def lost_password(token, recipient=nil)
302 set_language_if_valid(token.user.language)
302 set_language_if_valid(token.user.language)
303 recipient ||= token.user.mail
303 recipient ||= token.user.mail
304 @token = token
304 @token = token
305 @url = url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
305 @url = url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
306 mail :to => recipient,
306 mail :to => recipient,
307 :subject => l(:mail_subject_lost_password, Setting.app_title)
307 :subject => l(:mail_subject_lost_password, Setting.app_title)
308 end
308 end
309
309
310 def register(token)
310 def register(token)
311 set_language_if_valid(token.user.language)
311 set_language_if_valid(token.user.language)
312 @token = token
312 @token = token
313 @url = url_for(:controller => 'account', :action => 'activate', :token => token.value)
313 @url = url_for(:controller => 'account', :action => 'activate', :token => token.value)
314 mail :to => token.user.mail,
314 mail :to => token.user.mail,
315 :subject => l(:mail_subject_register, Setting.app_title)
315 :subject => l(:mail_subject_register, Setting.app_title)
316 end
316 end
317
317
318 def test_email(user)
318 def test_email(user)
319 set_language_if_valid(user.language)
319 set_language_if_valid(user.language)
320 @url = url_for(:controller => 'welcome')
320 @url = url_for(:controller => 'welcome')
321 mail :to => user.mail,
321 mail :to => user.mail,
322 :subject => 'Redmine test'
322 :subject => 'Redmine test'
323 end
323 end
324
324
325 # Sends reminders to issue assignees
325 # Sends reminders to issue assignees
326 # Available options:
326 # Available options:
327 # * :days => how many days in the future to remind about (defaults to 7)
327 # * :days => how many days in the future to remind about (defaults to 7)
328 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
328 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
329 # * :project => id or identifier of project to process (defaults to all projects)
329 # * :project => id or identifier of project to process (defaults to all projects)
330 # * :users => array of user/group ids who should be reminded
330 # * :users => array of user/group ids who should be reminded
331 # * :version => name of target version for filtering issues (defaults to none)
331 # * :version => name of target version for filtering issues (defaults to none)
332 def self.reminders(options={})
332 def self.reminders(options={})
333 days = options[:days] || 7
333 days = options[:days] || 7
334 project = options[:project] ? Project.find(options[:project]) : nil
334 project = options[:project] ? Project.find(options[:project]) : nil
335 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
335 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
336 target_version_id = options[:version] ? Version.named(options[:version]).pluck(:id) : nil
336 target_version_id = options[:version] ? Version.named(options[:version]).pluck(:id) : nil
337 if options[:version] && target_version_id.blank?
337 if options[:version] && target_version_id.blank?
338 raise ActiveRecord::RecordNotFound.new("Couldn't find Version with named #{options[:version]}")
338 raise ActiveRecord::RecordNotFound.new("Couldn't find Version with named #{options[:version]}")
339 end
339 end
340 user_ids = options[:users]
340 user_ids = options[:users]
341
341
342 scope = Issue.open.where("#{Issue.table_name}.assigned_to_id IS NOT NULL" +
342 scope = Issue.open.where("#{Issue.table_name}.assigned_to_id IS NOT NULL" +
343 " AND #{Project.table_name}.status = #{Project::STATUS_ACTIVE}" +
343 " AND #{Project.table_name}.status = #{Project::STATUS_ACTIVE}" +
344 " AND #{Issue.table_name}.due_date <= ?", days.day.from_now.to_date
344 " AND #{Issue.table_name}.due_date <= ?", days.day.from_now.to_date
345 )
345 )
346 scope = scope.where(:assigned_to_id => user_ids) if user_ids.present?
346 scope = scope.where(:assigned_to_id => user_ids) if user_ids.present?
347 scope = scope.where(:project_id => project.id) if project
347 scope = scope.where(:project_id => project.id) if project
348 scope = scope.where(:fixed_version_id => target_version_id) if target_version_id.present?
348 scope = scope.where(:fixed_version_id => target_version_id) if target_version_id.present?
349 scope = scope.where(:tracker_id => tracker.id) if tracker
349 scope = scope.where(:tracker_id => tracker.id) if tracker
350 issues_by_assignee = scope.includes(:status, :assigned_to, :project, :tracker).
350 issues_by_assignee = scope.includes(:status, :assigned_to, :project, :tracker).
351 group_by(&:assigned_to)
351 group_by(&:assigned_to)
352 issues_by_assignee.keys.each do |assignee|
352 issues_by_assignee.keys.each do |assignee|
353 if assignee.is_a?(Group)
353 if assignee.is_a?(Group)
354 assignee.users.each do |user|
354 assignee.users.each do |user|
355 issues_by_assignee[user] ||= []
355 issues_by_assignee[user] ||= []
356 issues_by_assignee[user] += issues_by_assignee[assignee]
356 issues_by_assignee[user] += issues_by_assignee[assignee]
357 end
357 end
358 end
358 end
359 end
359 end
360
360
361 issues_by_assignee.each do |assignee, issues|
361 issues_by_assignee.each do |assignee, issues|
362 reminder(assignee, issues, days).deliver if assignee.is_a?(User) && assignee.active?
362 reminder(assignee, issues, days).deliver if assignee.is_a?(User) && assignee.active?
363 end
363 end
364 end
364 end
365
365
366 # Activates/desactivates email deliveries during +block+
366 # Activates/desactivates email deliveries during +block+
367 def self.with_deliveries(enabled = true, &block)
367 def self.with_deliveries(enabled = true, &block)
368 was_enabled = ActionMailer::Base.perform_deliveries
368 was_enabled = ActionMailer::Base.perform_deliveries
369 ActionMailer::Base.perform_deliveries = !!enabled
369 ActionMailer::Base.perform_deliveries = !!enabled
370 yield
370 yield
371 ensure
371 ensure
372 ActionMailer::Base.perform_deliveries = was_enabled
372 ActionMailer::Base.perform_deliveries = was_enabled
373 end
373 end
374
374
375 # Sends emails synchronously in the given block
375 # Sends emails synchronously in the given block
376 def self.with_synched_deliveries(&block)
376 def self.with_synched_deliveries(&block)
377 saved_method = ActionMailer::Base.delivery_method
377 saved_method = ActionMailer::Base.delivery_method
378 if m = saved_method.to_s.match(%r{^async_(.+)$})
378 if m = saved_method.to_s.match(%r{^async_(.+)$})
379 synched_method = m[1]
379 synched_method = m[1]
380 ActionMailer::Base.delivery_method = synched_method.to_sym
380 ActionMailer::Base.delivery_method = synched_method.to_sym
381 ActionMailer::Base.send "#{synched_method}_settings=", ActionMailer::Base.send("async_#{synched_method}_settings")
381 ActionMailer::Base.send "#{synched_method}_settings=", ActionMailer::Base.send("async_#{synched_method}_settings")
382 end
382 end
383 yield
383 yield
384 ensure
384 ensure
385 ActionMailer::Base.delivery_method = saved_method
385 ActionMailer::Base.delivery_method = saved_method
386 end
386 end
387
387
388 def mail(headers={}, &block)
388 def mail(headers={}, &block)
389 headers.reverse_merge! 'X-Mailer' => 'Redmine',
389 headers.reverse_merge! 'X-Mailer' => 'Redmine',
390 'X-Redmine-Host' => Setting.host_name,
390 'X-Redmine-Host' => Setting.host_name,
391 'X-Redmine-Site' => Setting.app_title,
391 'X-Redmine-Site' => Setting.app_title,
392 'X-Auto-Response-Suppress' => 'OOF',
392 'X-Auto-Response-Suppress' => 'All',
393 'Auto-Submitted' => 'auto-generated',
393 'Auto-Submitted' => 'auto-generated',
394 'From' => Setting.mail_from,
394 'From' => Setting.mail_from,
395 'List-Id' => "<#{Setting.mail_from.to_s.gsub('@', '.')}>"
395 'List-Id' => "<#{Setting.mail_from.to_s.gsub('@', '.')}>"
396
396
397 # Replaces users with their email addresses
397 # Replaces users with their email addresses
398 [:to, :cc, :bcc].each do |key|
398 [:to, :cc, :bcc].each do |key|
399 if headers[key].present?
399 if headers[key].present?
400 headers[key] = self.class.email_addresses(headers[key])
400 headers[key] = self.class.email_addresses(headers[key])
401 end
401 end
402 end
402 end
403
403
404 # Removes the author from the recipients and cc
404 # Removes the author from the recipients and cc
405 # if the author does not want to receive notifications
405 # if the author does not want to receive notifications
406 # about what the author do
406 # about what the author do
407 if @author && @author.logged? && @author.pref.no_self_notified
407 if @author && @author.logged? && @author.pref.no_self_notified
408 addresses = @author.mails
408 addresses = @author.mails
409 headers[:to] -= addresses if headers[:to].is_a?(Array)
409 headers[:to] -= addresses if headers[:to].is_a?(Array)
410 headers[:cc] -= addresses if headers[:cc].is_a?(Array)
410 headers[:cc] -= addresses if headers[:cc].is_a?(Array)
411 end
411 end
412
412
413 if @author && @author.logged?
413 if @author && @author.logged?
414 redmine_headers 'Sender' => @author.login
414 redmine_headers 'Sender' => @author.login
415 end
415 end
416
416
417 # Blind carbon copy recipients
417 # Blind carbon copy recipients
418 if Setting.bcc_recipients?
418 if Setting.bcc_recipients?
419 headers[:bcc] = [headers[:to], headers[:cc]].flatten.uniq.reject(&:blank?)
419 headers[:bcc] = [headers[:to], headers[:cc]].flatten.uniq.reject(&:blank?)
420 headers[:to] = nil
420 headers[:to] = nil
421 headers[:cc] = nil
421 headers[:cc] = nil
422 end
422 end
423
423
424 if @message_id_object
424 if @message_id_object
425 headers[:message_id] = "<#{self.class.message_id_for(@message_id_object)}>"
425 headers[:message_id] = "<#{self.class.message_id_for(@message_id_object)}>"
426 end
426 end
427 if @references_objects
427 if @references_objects
428 headers[:references] = @references_objects.collect {|o| "<#{self.class.references_for(o)}>"}.join(' ')
428 headers[:references] = @references_objects.collect {|o| "<#{self.class.references_for(o)}>"}.join(' ')
429 end
429 end
430
430
431 m = if block_given?
431 m = if block_given?
432 super headers, &block
432 super headers, &block
433 else
433 else
434 super headers do |format|
434 super headers do |format|
435 format.text
435 format.text
436 format.html unless Setting.plain_text_mail?
436 format.html unless Setting.plain_text_mail?
437 end
437 end
438 end
438 end
439 set_language_if_valid @initial_language
439 set_language_if_valid @initial_language
440
440
441 m
441 m
442 end
442 end
443
443
444 def initialize(*args)
444 def initialize(*args)
445 @initial_language = current_language
445 @initial_language = current_language
446 set_language_if_valid Setting.default_language
446 set_language_if_valid Setting.default_language
447 super
447 super
448 end
448 end
449
449
450 def self.deliver_mail(mail)
450 def self.deliver_mail(mail)
451 return false if mail.to.blank? && mail.cc.blank? && mail.bcc.blank?
451 return false if mail.to.blank? && mail.cc.blank? && mail.bcc.blank?
452 begin
452 begin
453 # Log errors when raise_delivery_errors is set to false, Rails does not
453 # Log errors when raise_delivery_errors is set to false, Rails does not
454 mail.raise_delivery_errors = true
454 mail.raise_delivery_errors = true
455 super
455 super
456 rescue Exception => e
456 rescue Exception => e
457 if ActionMailer::Base.raise_delivery_errors
457 if ActionMailer::Base.raise_delivery_errors
458 raise e
458 raise e
459 else
459 else
460 Rails.logger.error "Email delivery error: #{e.message}"
460 Rails.logger.error "Email delivery error: #{e.message}"
461 end
461 end
462 end
462 end
463 end
463 end
464
464
465 def self.method_missing(method, *args, &block)
465 def self.method_missing(method, *args, &block)
466 if m = method.to_s.match(%r{^deliver_(.+)$})
466 if m = method.to_s.match(%r{^deliver_(.+)$})
467 ActiveSupport::Deprecation.warn "Mailer.deliver_#{m[1]}(*args) is deprecated. Use Mailer.#{m[1]}(*args).deliver instead."
467 ActiveSupport::Deprecation.warn "Mailer.deliver_#{m[1]}(*args) is deprecated. Use Mailer.#{m[1]}(*args).deliver instead."
468 send(m[1], *args).deliver
468 send(m[1], *args).deliver
469 else
469 else
470 super
470 super
471 end
471 end
472 end
472 end
473
473
474 # Returns an array of email addresses to notify by
474 # Returns an array of email addresses to notify by
475 # replacing users in arg with their notified email addresses
475 # replacing users in arg with their notified email addresses
476 #
476 #
477 # Example:
477 # Example:
478 # Mailer.email_addresses(users)
478 # Mailer.email_addresses(users)
479 # => ["foo@example.net", "bar@example.net"]
479 # => ["foo@example.net", "bar@example.net"]
480 def self.email_addresses(arg)
480 def self.email_addresses(arg)
481 arr = Array.wrap(arg)
481 arr = Array.wrap(arg)
482 mails = arr.reject {|a| a.is_a? Principal}
482 mails = arr.reject {|a| a.is_a? Principal}
483 users = arr - mails
483 users = arr - mails
484 if users.any?
484 if users.any?
485 mails += EmailAddress.
485 mails += EmailAddress.
486 where(:user_id => users.map(&:id)).
486 where(:user_id => users.map(&:id)).
487 where("is_default = ? OR notify = ?", true, true).
487 where("is_default = ? OR notify = ?", true, true).
488 pluck(:address)
488 pluck(:address)
489 end
489 end
490 mails
490 mails
491 end
491 end
492
492
493 private
493 private
494
494
495 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
495 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
496 def redmine_headers(h)
496 def redmine_headers(h)
497 h.each { |k,v| headers["X-Redmine-#{k}"] = v.to_s }
497 h.each { |k,v| headers["X-Redmine-#{k}"] = v.to_s }
498 end
498 end
499
499
500 def self.token_for(object, rand=true)
500 def self.token_for(object, rand=true)
501 timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on)
501 timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on)
502 hash = [
502 hash = [
503 "redmine",
503 "redmine",
504 "#{object.class.name.demodulize.underscore}-#{object.id}",
504 "#{object.class.name.demodulize.underscore}-#{object.id}",
505 timestamp.strftime("%Y%m%d%H%M%S")
505 timestamp.strftime("%Y%m%d%H%M%S")
506 ]
506 ]
507 if rand
507 if rand
508 hash << Redmine::Utils.random_hex(8)
508 hash << Redmine::Utils.random_hex(8)
509 end
509 end
510 host = Setting.mail_from.to_s.strip.gsub(%r{^.*@|>}, '')
510 host = Setting.mail_from.to_s.strip.gsub(%r{^.*@|>}, '')
511 host = "#{::Socket.gethostname}.redmine" if host.empty?
511 host = "#{::Socket.gethostname}.redmine" if host.empty?
512 "#{hash.join('.')}@#{host}"
512 "#{hash.join('.')}@#{host}"
513 end
513 end
514
514
515 # Returns a Message-Id for the given object
515 # Returns a Message-Id for the given object
516 def self.message_id_for(object)
516 def self.message_id_for(object)
517 token_for(object, true)
517 token_for(object, true)
518 end
518 end
519
519
520 # Returns a uniq token for a given object referenced by all notifications
520 # Returns a uniq token for a given object referenced by all notifications
521 # related to this object
521 # related to this object
522 def self.references_for(object)
522 def self.references_for(object)
523 token_for(object, false)
523 token_for(object, false)
524 end
524 end
525
525
526 def message_id(object)
526 def message_id(object)
527 @message_id_object = object
527 @message_id_object = object
528 end
528 end
529
529
530 def references(object)
530 def references(object)
531 @references_objects ||= []
531 @references_objects ||= []
532 @references_objects << object
532 @references_objects << object
533 end
533 end
534
534
535 def mylogger
535 def mylogger
536 Rails.logger
536 Rails.logger
537 end
537 end
538 end
538 end
@@ -1,971 +1,970
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2015 Jean-Philippe Lang
4 # Copyright (C) 2006-2015 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require File.expand_path('../../test_helper', __FILE__)
20 require File.expand_path('../../test_helper', __FILE__)
21
21
22 class MailHandlerTest < ActiveSupport::TestCase
22 class MailHandlerTest < ActiveSupport::TestCase
23 fixtures :users, :projects, :enabled_modules, :roles,
23 fixtures :users, :projects, :enabled_modules, :roles,
24 :members, :member_roles, :users,
24 :members, :member_roles, :users,
25 :email_addresses,
25 :email_addresses,
26 :issues, :issue_statuses,
26 :issues, :issue_statuses,
27 :workflows, :trackers, :projects_trackers,
27 :workflows, :trackers, :projects_trackers,
28 :versions, :enumerations, :issue_categories,
28 :versions, :enumerations, :issue_categories,
29 :custom_fields, :custom_fields_trackers, :custom_fields_projects,
29 :custom_fields, :custom_fields_trackers, :custom_fields_projects,
30 :boards, :messages
30 :boards, :messages
31
31
32 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
32 FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler'
33
33
34 def setup
34 def setup
35 ActionMailer::Base.deliveries.clear
35 ActionMailer::Base.deliveries.clear
36 Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
36 Setting.notified_events = Redmine::Notifiable.all.collect(&:name)
37 end
37 end
38
38
39 def teardown
39 def teardown
40 Setting.clear_cache
40 Setting.clear_cache
41 end
41 end
42
42
43 def test_add_issue
43 def test_add_issue
44 ActionMailer::Base.deliveries.clear
44 ActionMailer::Base.deliveries.clear
45 lft1 = new_issue_lft
45 lft1 = new_issue_lft
46 # This email contains: 'Project: onlinestore'
46 # This email contains: 'Project: onlinestore'
47 issue = submit_email('ticket_on_given_project.eml')
47 issue = submit_email('ticket_on_given_project.eml')
48 assert issue.is_a?(Issue)
48 assert issue.is_a?(Issue)
49 assert !issue.new_record?
49 assert !issue.new_record?
50 issue.reload
50 issue.reload
51 assert_equal Project.find(2), issue.project
51 assert_equal Project.find(2), issue.project
52 assert_equal issue.project.trackers.first, issue.tracker
52 assert_equal issue.project.trackers.first, issue.tracker
53 assert_equal 'New ticket on a given project', issue.subject
53 assert_equal 'New ticket on a given project', issue.subject
54 assert_equal User.find_by_login('jsmith'), issue.author
54 assert_equal User.find_by_login('jsmith'), issue.author
55 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
55 assert_equal IssueStatus.find_by_name('Resolved'), issue.status
56 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
56 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
57 assert_equal '2010-01-01', issue.start_date.to_s
57 assert_equal '2010-01-01', issue.start_date.to_s
58 assert_equal '2010-12-31', issue.due_date.to_s
58 assert_equal '2010-12-31', issue.due_date.to_s
59 assert_equal User.find_by_login('jsmith'), issue.assigned_to
59 assert_equal User.find_by_login('jsmith'), issue.assigned_to
60 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
60 assert_equal Version.find_by_name('Alpha'), issue.fixed_version
61 assert_equal 2.5, issue.estimated_hours
61 assert_equal 2.5, issue.estimated_hours
62 assert_equal 30, issue.done_ratio
62 assert_equal 30, issue.done_ratio
63 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
63 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
64 # keywords should be removed from the email body
64 # keywords should be removed from the email body
65 assert !issue.description.match(/^Project:/i)
65 assert !issue.description.match(/^Project:/i)
66 assert !issue.description.match(/^Status:/i)
66 assert !issue.description.match(/^Status:/i)
67 assert !issue.description.match(/^Start Date:/i)
67 assert !issue.description.match(/^Start Date:/i)
68 # Email notification should be sent
68 # Email notification should be sent
69 mail = ActionMailer::Base.deliveries.last
69 mail = ActionMailer::Base.deliveries.last
70 assert_not_nil mail
70 assert_not_nil mail
71 assert mail.subject.include?("##{issue.id}")
71 assert mail.subject.include?("##{issue.id}")
72 assert mail.subject.include?('New ticket on a given project')
72 assert mail.subject.include?('New ticket on a given project')
73 end
73 end
74
74
75 def test_add_issue_with_default_tracker
75 def test_add_issue_with_default_tracker
76 # This email contains: 'Project: onlinestore'
76 # This email contains: 'Project: onlinestore'
77 issue = submit_email(
77 issue = submit_email(
78 'ticket_on_given_project.eml',
78 'ticket_on_given_project.eml',
79 :issue => {:tracker => 'Support request'}
79 :issue => {:tracker => 'Support request'}
80 )
80 )
81 assert issue.is_a?(Issue)
81 assert issue.is_a?(Issue)
82 assert !issue.new_record?
82 assert !issue.new_record?
83 issue.reload
83 issue.reload
84 assert_equal 'Support request', issue.tracker.name
84 assert_equal 'Support request', issue.tracker.name
85 end
85 end
86
86
87 def test_add_issue_with_status
87 def test_add_issue_with_status
88 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
88 # This email contains: 'Project: onlinestore' and 'Status: Resolved'
89 issue = submit_email('ticket_on_given_project.eml')
89 issue = submit_email('ticket_on_given_project.eml')
90 assert issue.is_a?(Issue)
90 assert issue.is_a?(Issue)
91 assert !issue.new_record?
91 assert !issue.new_record?
92 issue.reload
92 issue.reload
93 assert_equal Project.find(2), issue.project
93 assert_equal Project.find(2), issue.project
94 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
94 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
95 end
95 end
96
96
97 def test_add_issue_with_attributes_override
97 def test_add_issue_with_attributes_override
98 issue = submit_email(
98 issue = submit_email(
99 'ticket_with_attributes.eml',
99 'ticket_with_attributes.eml',
100 :allow_override => 'tracker,category,priority'
100 :allow_override => 'tracker,category,priority'
101 )
101 )
102 assert issue.is_a?(Issue)
102 assert issue.is_a?(Issue)
103 assert !issue.new_record?
103 assert !issue.new_record?
104 issue.reload
104 issue.reload
105 assert_equal 'New ticket on a given project', issue.subject
105 assert_equal 'New ticket on a given project', issue.subject
106 assert_equal User.find_by_login('jsmith'), issue.author
106 assert_equal User.find_by_login('jsmith'), issue.author
107 assert_equal Project.find(2), issue.project
107 assert_equal Project.find(2), issue.project
108 assert_equal 'Feature request', issue.tracker.to_s
108 assert_equal 'Feature request', issue.tracker.to_s
109 assert_equal 'Stock management', issue.category.to_s
109 assert_equal 'Stock management', issue.category.to_s
110 assert_equal 'Urgent', issue.priority.to_s
110 assert_equal 'Urgent', issue.priority.to_s
111 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
111 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
112 end
112 end
113
113
114 def test_add_issue_with_group_assignment
114 def test_add_issue_with_group_assignment
115 with_settings :issue_group_assignment => '1' do
115 with_settings :issue_group_assignment => '1' do
116 issue = submit_email('ticket_on_given_project.eml') do |email|
116 issue = submit_email('ticket_on_given_project.eml') do |email|
117 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
117 email.gsub!('Assigned to: John Smith', 'Assigned to: B Team')
118 end
118 end
119 assert issue.is_a?(Issue)
119 assert issue.is_a?(Issue)
120 assert !issue.new_record?
120 assert !issue.new_record?
121 issue.reload
121 issue.reload
122 assert_equal Group.find(11), issue.assigned_to
122 assert_equal Group.find(11), issue.assigned_to
123 end
123 end
124 end
124 end
125
125
126 def test_add_issue_with_partial_attributes_override
126 def test_add_issue_with_partial_attributes_override
127 issue = submit_email(
127 issue = submit_email(
128 'ticket_with_attributes.eml',
128 'ticket_with_attributes.eml',
129 :issue => {:priority => 'High'},
129 :issue => {:priority => 'High'},
130 :allow_override => ['tracker']
130 :allow_override => ['tracker']
131 )
131 )
132 assert issue.is_a?(Issue)
132 assert issue.is_a?(Issue)
133 assert !issue.new_record?
133 assert !issue.new_record?
134 issue.reload
134 issue.reload
135 assert_equal 'New ticket on a given project', issue.subject
135 assert_equal 'New ticket on a given project', issue.subject
136 assert_equal User.find_by_login('jsmith'), issue.author
136 assert_equal User.find_by_login('jsmith'), issue.author
137 assert_equal Project.find(2), issue.project
137 assert_equal Project.find(2), issue.project
138 assert_equal 'Feature request', issue.tracker.to_s
138 assert_equal 'Feature request', issue.tracker.to_s
139 assert_nil issue.category
139 assert_nil issue.category
140 assert_equal 'High', issue.priority.to_s
140 assert_equal 'High', issue.priority.to_s
141 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
141 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
142 end
142 end
143
143
144 def test_add_issue_with_spaces_between_attribute_and_separator
144 def test_add_issue_with_spaces_between_attribute_and_separator
145 issue = submit_email(
145 issue = submit_email(
146 'ticket_with_spaces_between_attribute_and_separator.eml',
146 'ticket_with_spaces_between_attribute_and_separator.eml',
147 :allow_override => 'tracker,category,priority'
147 :allow_override => 'tracker,category,priority'
148 )
148 )
149 assert issue.is_a?(Issue)
149 assert issue.is_a?(Issue)
150 assert !issue.new_record?
150 assert !issue.new_record?
151 issue.reload
151 issue.reload
152 assert_equal 'New ticket on a given project', issue.subject
152 assert_equal 'New ticket on a given project', issue.subject
153 assert_equal User.find_by_login('jsmith'), issue.author
153 assert_equal User.find_by_login('jsmith'), issue.author
154 assert_equal Project.find(2), issue.project
154 assert_equal Project.find(2), issue.project
155 assert_equal 'Feature request', issue.tracker.to_s
155 assert_equal 'Feature request', issue.tracker.to_s
156 assert_equal 'Stock management', issue.category.to_s
156 assert_equal 'Stock management', issue.category.to_s
157 assert_equal 'Urgent', issue.priority.to_s
157 assert_equal 'Urgent', issue.priority.to_s
158 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
158 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
159 end
159 end
160
160
161 def test_add_issue_with_attachment_to_specific_project
161 def test_add_issue_with_attachment_to_specific_project
162 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
162 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
163 assert issue.is_a?(Issue)
163 assert issue.is_a?(Issue)
164 assert !issue.new_record?
164 assert !issue.new_record?
165 issue.reload
165 issue.reload
166 assert_equal 'Ticket created by email with attachment', issue.subject
166 assert_equal 'Ticket created by email with attachment', issue.subject
167 assert_equal User.find_by_login('jsmith'), issue.author
167 assert_equal User.find_by_login('jsmith'), issue.author
168 assert_equal Project.find(2), issue.project
168 assert_equal Project.find(2), issue.project
169 assert_equal 'This is a new ticket with attachments', issue.description
169 assert_equal 'This is a new ticket with attachments', issue.description
170 # Attachment properties
170 # Attachment properties
171 assert_equal 1, issue.attachments.size
171 assert_equal 1, issue.attachments.size
172 assert_equal 'Paella.jpg', issue.attachments.first.filename
172 assert_equal 'Paella.jpg', issue.attachments.first.filename
173 assert_equal 'image/jpeg', issue.attachments.first.content_type
173 assert_equal 'image/jpeg', issue.attachments.first.content_type
174 assert_equal 10790, issue.attachments.first.filesize
174 assert_equal 10790, issue.attachments.first.filesize
175 end
175 end
176
176
177 def test_add_issue_with_custom_fields
177 def test_add_issue_with_custom_fields
178 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
178 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'onlinestore'})
179 assert issue.is_a?(Issue)
179 assert issue.is_a?(Issue)
180 assert !issue.new_record?
180 assert !issue.new_record?
181 issue.reload
181 issue.reload
182 assert_equal 'New ticket with custom field values', issue.subject
182 assert_equal 'New ticket with custom field values', issue.subject
183 assert_equal 'PostgreSQL', issue.custom_field_value(1)
183 assert_equal 'PostgreSQL', issue.custom_field_value(1)
184 assert_equal 'Value for a custom field', issue.custom_field_value(2)
184 assert_equal 'Value for a custom field', issue.custom_field_value(2)
185 assert !issue.description.match(/^searchable field:/i)
185 assert !issue.description.match(/^searchable field:/i)
186 end
186 end
187
187
188 def test_add_issue_with_version_custom_fields
188 def test_add_issue_with_version_custom_fields
189 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3])
189 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3])
190
190
191 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'ecookbook'}) do |email|
191 issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'ecookbook'}) do |email|
192 email << "Affected version: 1.0\n"
192 email << "Affected version: 1.0\n"
193 end
193 end
194 assert issue.is_a?(Issue)
194 assert issue.is_a?(Issue)
195 assert !issue.new_record?
195 assert !issue.new_record?
196 issue.reload
196 issue.reload
197 assert_equal '2', issue.custom_field_value(field)
197 assert_equal '2', issue.custom_field_value(field)
198 end
198 end
199
199
200 def test_add_issue_should_match_assignee_on_display_name
200 def test_add_issue_should_match_assignee_on_display_name
201 user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz')
201 user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz')
202 User.add_to_project(user, Project.find(2))
202 User.add_to_project(user, Project.find(2))
203 issue = submit_email('ticket_on_given_project.eml') do |email|
203 issue = submit_email('ticket_on_given_project.eml') do |email|
204 email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz')
204 email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz')
205 end
205 end
206 assert issue.is_a?(Issue)
206 assert issue.is_a?(Issue)
207 assert_equal user, issue.assigned_to
207 assert_equal user, issue.assigned_to
208 end
208 end
209
209
210 def test_add_issue_should_set_default_start_date
210 def test_add_issue_should_set_default_start_date
211 with_settings :default_issue_start_date_to_creation_date => '1' do
211 with_settings :default_issue_start_date_to_creation_date => '1' do
212 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
212 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
213 assert issue.is_a?(Issue)
213 assert issue.is_a?(Issue)
214 assert_equal Date.today, issue.start_date
214 assert_equal Date.today, issue.start_date
215 end
215 end
216 end
216 end
217
217
218 def test_add_issue_with_cc
218 def test_add_issue_with_cc
219 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
219 issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'})
220 assert issue.is_a?(Issue)
220 assert issue.is_a?(Issue)
221 assert !issue.new_record?
221 assert !issue.new_record?
222 issue.reload
222 issue.reload
223 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
223 assert issue.watched_by?(User.find_by_mail('dlopper@somenet.foo'))
224 assert_equal 1, issue.watcher_user_ids.size
224 assert_equal 1, issue.watcher_user_ids.size
225 end
225 end
226
226
227 def test_add_issue_from_additional_email_address
227 def test_add_issue_from_additional_email_address
228 user = User.find(2)
228 user = User.find(2)
229 user.mail = 'mainaddress@somenet.foo'
229 user.mail = 'mainaddress@somenet.foo'
230 user.save!
230 user.save!
231 EmailAddress.create!(:user => user, :address => 'jsmith@somenet.foo')
231 EmailAddress.create!(:user => user, :address => 'jsmith@somenet.foo')
232
232
233 issue = submit_email('ticket_on_given_project.eml')
233 issue = submit_email('ticket_on_given_project.eml')
234 assert issue
234 assert issue
235 assert_equal user, issue.author
235 assert_equal user, issue.author
236 end
236 end
237
237
238 def test_add_issue_by_unknown_user
238 def test_add_issue_by_unknown_user
239 assert_no_difference 'User.count' do
239 assert_no_difference 'User.count' do
240 assert_equal false,
240 assert_equal false,
241 submit_email(
241 submit_email(
242 'ticket_by_unknown_user.eml',
242 'ticket_by_unknown_user.eml',
243 :issue => {:project => 'ecookbook'}
243 :issue => {:project => 'ecookbook'}
244 )
244 )
245 end
245 end
246 end
246 end
247
247
248 def test_add_issue_by_anonymous_user
248 def test_add_issue_by_anonymous_user
249 Role.anonymous.add_permission!(:add_issues)
249 Role.anonymous.add_permission!(:add_issues)
250 assert_no_difference 'User.count' do
250 assert_no_difference 'User.count' do
251 issue = submit_email(
251 issue = submit_email(
252 'ticket_by_unknown_user.eml',
252 'ticket_by_unknown_user.eml',
253 :issue => {:project => 'ecookbook'},
253 :issue => {:project => 'ecookbook'},
254 :unknown_user => 'accept'
254 :unknown_user => 'accept'
255 )
255 )
256 assert issue.is_a?(Issue)
256 assert issue.is_a?(Issue)
257 assert issue.author.anonymous?
257 assert issue.author.anonymous?
258 end
258 end
259 end
259 end
260
260
261 def test_add_issue_by_anonymous_user_with_no_from_address
261 def test_add_issue_by_anonymous_user_with_no_from_address
262 Role.anonymous.add_permission!(:add_issues)
262 Role.anonymous.add_permission!(:add_issues)
263 assert_no_difference 'User.count' do
263 assert_no_difference 'User.count' do
264 issue = submit_email(
264 issue = submit_email(
265 'ticket_by_empty_user.eml',
265 'ticket_by_empty_user.eml',
266 :issue => {:project => 'ecookbook'},
266 :issue => {:project => 'ecookbook'},
267 :unknown_user => 'accept'
267 :unknown_user => 'accept'
268 )
268 )
269 assert issue.is_a?(Issue)
269 assert issue.is_a?(Issue)
270 assert issue.author.anonymous?
270 assert issue.author.anonymous?
271 end
271 end
272 end
272 end
273
273
274 def test_add_issue_by_anonymous_user_on_private_project
274 def test_add_issue_by_anonymous_user_on_private_project
275 Role.anonymous.add_permission!(:add_issues)
275 Role.anonymous.add_permission!(:add_issues)
276 assert_no_difference 'User.count' do
276 assert_no_difference 'User.count' do
277 assert_no_difference 'Issue.count' do
277 assert_no_difference 'Issue.count' do
278 assert_equal false,
278 assert_equal false,
279 submit_email(
279 submit_email(
280 'ticket_by_unknown_user.eml',
280 'ticket_by_unknown_user.eml',
281 :issue => {:project => 'onlinestore'},
281 :issue => {:project => 'onlinestore'},
282 :unknown_user => 'accept'
282 :unknown_user => 'accept'
283 )
283 )
284 end
284 end
285 end
285 end
286 end
286 end
287
287
288 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
288 def test_add_issue_by_anonymous_user_on_private_project_without_permission_check
289 lft1 = new_issue_lft
289 lft1 = new_issue_lft
290 assert_no_difference 'User.count' do
290 assert_no_difference 'User.count' do
291 assert_difference 'Issue.count' do
291 assert_difference 'Issue.count' do
292 issue = submit_email(
292 issue = submit_email(
293 'ticket_by_unknown_user.eml',
293 'ticket_by_unknown_user.eml',
294 :issue => {:project => 'onlinestore'},
294 :issue => {:project => 'onlinestore'},
295 :no_permission_check => '1',
295 :no_permission_check => '1',
296 :unknown_user => 'accept'
296 :unknown_user => 'accept'
297 )
297 )
298 assert issue.is_a?(Issue)
298 assert issue.is_a?(Issue)
299 assert issue.author.anonymous?
299 assert issue.author.anonymous?
300 assert !issue.project.is_public?
300 assert !issue.project.is_public?
301 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
301 assert_equal [issue.id, lft1, lft1 + 1], [issue.root_id, issue.lft, issue.rgt]
302 end
302 end
303 end
303 end
304 end
304 end
305
305
306 def test_add_issue_by_created_user
306 def test_add_issue_by_created_user
307 Setting.default_language = 'en'
307 Setting.default_language = 'en'
308 assert_difference 'User.count' do
308 assert_difference 'User.count' do
309 issue = submit_email(
309 issue = submit_email(
310 'ticket_by_unknown_user.eml',
310 'ticket_by_unknown_user.eml',
311 :issue => {:project => 'ecookbook'},
311 :issue => {:project => 'ecookbook'},
312 :unknown_user => 'create'
312 :unknown_user => 'create'
313 )
313 )
314 assert issue.is_a?(Issue)
314 assert issue.is_a?(Issue)
315 assert issue.author.active?
315 assert issue.author.active?
316 assert_equal 'john.doe@somenet.foo', issue.author.mail
316 assert_equal 'john.doe@somenet.foo', issue.author.mail
317 assert_equal 'John', issue.author.firstname
317 assert_equal 'John', issue.author.firstname
318 assert_equal 'Doe', issue.author.lastname
318 assert_equal 'Doe', issue.author.lastname
319
319
320 # account information
320 # account information
321 email = ActionMailer::Base.deliveries.first
321 email = ActionMailer::Base.deliveries.first
322 assert_not_nil email
322 assert_not_nil email
323 assert email.subject.include?('account activation')
323 assert email.subject.include?('account activation')
324 login = mail_body(email).match(/\* Login: (.*)$/)[1].strip
324 login = mail_body(email).match(/\* Login: (.*)$/)[1].strip
325 password = mail_body(email).match(/\* Password: (.*)$/)[1].strip
325 password = mail_body(email).match(/\* Password: (.*)$/)[1].strip
326 assert_equal issue.author, User.try_to_login(login, password)
326 assert_equal issue.author, User.try_to_login(login, password)
327 end
327 end
328 end
328 end
329
329
330 def test_created_user_should_be_added_to_groups
330 def test_created_user_should_be_added_to_groups
331 group1 = Group.generate!
331 group1 = Group.generate!
332 group2 = Group.generate!
332 group2 = Group.generate!
333
333
334 assert_difference 'User.count' do
334 assert_difference 'User.count' do
335 submit_email(
335 submit_email(
336 'ticket_by_unknown_user.eml',
336 'ticket_by_unknown_user.eml',
337 :issue => {:project => 'ecookbook'},
337 :issue => {:project => 'ecookbook'},
338 :unknown_user => 'create',
338 :unknown_user => 'create',
339 :default_group => "#{group1.name},#{group2.name}"
339 :default_group => "#{group1.name},#{group2.name}"
340 )
340 )
341 end
341 end
342 user = User.order('id DESC').first
342 user = User.order('id DESC').first
343 assert_equal [group1, group2].sort, user.groups.sort
343 assert_equal [group1, group2].sort, user.groups.sort
344 end
344 end
345
345
346 def test_created_user_should_not_receive_account_information_with_no_account_info_option
346 def test_created_user_should_not_receive_account_information_with_no_account_info_option
347 assert_difference 'User.count' do
347 assert_difference 'User.count' do
348 submit_email(
348 submit_email(
349 'ticket_by_unknown_user.eml',
349 'ticket_by_unknown_user.eml',
350 :issue => {:project => 'ecookbook'},
350 :issue => {:project => 'ecookbook'},
351 :unknown_user => 'create',
351 :unknown_user => 'create',
352 :no_account_notice => '1'
352 :no_account_notice => '1'
353 )
353 )
354 end
354 end
355
355
356 # only 1 email for the new issue notification
356 # only 1 email for the new issue notification
357 assert_equal 1, ActionMailer::Base.deliveries.size
357 assert_equal 1, ActionMailer::Base.deliveries.size
358 email = ActionMailer::Base.deliveries.first
358 email = ActionMailer::Base.deliveries.first
359 assert_include 'Ticket by unknown user', email.subject
359 assert_include 'Ticket by unknown user', email.subject
360 end
360 end
361
361
362 def test_created_user_should_have_mail_notification_to_none_with_no_notification_option
362 def test_created_user_should_have_mail_notification_to_none_with_no_notification_option
363 assert_difference 'User.count' do
363 assert_difference 'User.count' do
364 submit_email(
364 submit_email(
365 'ticket_by_unknown_user.eml',
365 'ticket_by_unknown_user.eml',
366 :issue => {:project => 'ecookbook'},
366 :issue => {:project => 'ecookbook'},
367 :unknown_user => 'create',
367 :unknown_user => 'create',
368 :no_notification => '1'
368 :no_notification => '1'
369 )
369 )
370 end
370 end
371 user = User.order('id DESC').first
371 user = User.order('id DESC').first
372 assert_equal 'none', user.mail_notification
372 assert_equal 'none', user.mail_notification
373 end
373 end
374
374
375 def test_add_issue_without_from_header
375 def test_add_issue_without_from_header
376 Role.anonymous.add_permission!(:add_issues)
376 Role.anonymous.add_permission!(:add_issues)
377 assert_equal false, submit_email('ticket_without_from_header.eml')
377 assert_equal false, submit_email('ticket_without_from_header.eml')
378 end
378 end
379
379
380 def test_add_issue_with_invalid_attributes
380 def test_add_issue_with_invalid_attributes
381 with_settings :default_issue_start_date_to_creation_date => '0' do
381 with_settings :default_issue_start_date_to_creation_date => '0' do
382 issue = submit_email(
382 issue = submit_email(
383 'ticket_with_invalid_attributes.eml',
383 'ticket_with_invalid_attributes.eml',
384 :allow_override => 'tracker,category,priority'
384 :allow_override => 'tracker,category,priority'
385 )
385 )
386 assert issue.is_a?(Issue)
386 assert issue.is_a?(Issue)
387 assert !issue.new_record?
387 assert !issue.new_record?
388 issue.reload
388 issue.reload
389 assert_nil issue.assigned_to
389 assert_nil issue.assigned_to
390 assert_nil issue.start_date
390 assert_nil issue.start_date
391 assert_nil issue.due_date
391 assert_nil issue.due_date
392 assert_equal 0, issue.done_ratio
392 assert_equal 0, issue.done_ratio
393 assert_equal 'Normal', issue.priority.to_s
393 assert_equal 'Normal', issue.priority.to_s
394 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
394 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
395 end
395 end
396 end
396 end
397
397
398 def test_add_issue_with_invalid_project_should_be_assigned_to_default_project
398 def test_add_issue_with_invalid_project_should_be_assigned_to_default_project
399 issue = submit_email('ticket_on_given_project.eml', :issue => {:project => 'ecookbook'}, :allow_override => 'project') do |email|
399 issue = submit_email('ticket_on_given_project.eml', :issue => {:project => 'ecookbook'}, :allow_override => 'project') do |email|
400 email.gsub!(/^Project:.+$/, 'Project: invalid')
400 email.gsub!(/^Project:.+$/, 'Project: invalid')
401 end
401 end
402 assert issue.is_a?(Issue)
402 assert issue.is_a?(Issue)
403 assert !issue.new_record?
403 assert !issue.new_record?
404 assert_equal 'ecookbook', issue.project.identifier
404 assert_equal 'ecookbook', issue.project.identifier
405 end
405 end
406
406
407 def test_add_issue_with_localized_attributes
407 def test_add_issue_with_localized_attributes
408 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
408 User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr'
409 issue = submit_email(
409 issue = submit_email(
410 'ticket_with_localized_attributes.eml',
410 'ticket_with_localized_attributes.eml',
411 :allow_override => 'tracker,category,priority'
411 :allow_override => 'tracker,category,priority'
412 )
412 )
413 assert issue.is_a?(Issue)
413 assert issue.is_a?(Issue)
414 assert !issue.new_record?
414 assert !issue.new_record?
415 issue.reload
415 issue.reload
416 assert_equal 'New ticket on a given project', issue.subject
416 assert_equal 'New ticket on a given project', issue.subject
417 assert_equal User.find_by_login('jsmith'), issue.author
417 assert_equal User.find_by_login('jsmith'), issue.author
418 assert_equal Project.find(2), issue.project
418 assert_equal Project.find(2), issue.project
419 assert_equal 'Feature request', issue.tracker.to_s
419 assert_equal 'Feature request', issue.tracker.to_s
420 assert_equal 'Stock management', issue.category.to_s
420 assert_equal 'Stock management', issue.category.to_s
421 assert_equal 'Urgent', issue.priority.to_s
421 assert_equal 'Urgent', issue.priority.to_s
422 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
422 assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.')
423 end
423 end
424
424
425 def test_add_issue_with_japanese_keywords
425 def test_add_issue_with_japanese_keywords
426 ja_dev = "\xe9\x96\x8b\xe7\x99\xba".force_encoding('UTF-8')
426 ja_dev = "\xe9\x96\x8b\xe7\x99\xba".force_encoding('UTF-8')
427 tracker = Tracker.generate!(:name => ja_dev)
427 tracker = Tracker.generate!(:name => ja_dev)
428 Project.find(1).trackers << tracker
428 Project.find(1).trackers << tracker
429 issue = submit_email(
429 issue = submit_email(
430 'japanese_keywords_iso_2022_jp.eml',
430 'japanese_keywords_iso_2022_jp.eml',
431 :issue => {:project => 'ecookbook'},
431 :issue => {:project => 'ecookbook'},
432 :allow_override => 'tracker'
432 :allow_override => 'tracker'
433 )
433 )
434 assert_kind_of Issue, issue
434 assert_kind_of Issue, issue
435 assert_equal tracker, issue.tracker
435 assert_equal tracker, issue.tracker
436 end
436 end
437
437
438 def test_add_issue_from_apple_mail
438 def test_add_issue_from_apple_mail
439 issue = submit_email(
439 issue = submit_email(
440 'apple_mail_with_attachment.eml',
440 'apple_mail_with_attachment.eml',
441 :issue => {:project => 'ecookbook'}
441 :issue => {:project => 'ecookbook'}
442 )
442 )
443 assert_kind_of Issue, issue
443 assert_kind_of Issue, issue
444 assert_equal 1, issue.attachments.size
444 assert_equal 1, issue.attachments.size
445
445
446 attachment = issue.attachments.first
446 attachment = issue.attachments.first
447 assert_equal 'paella.jpg', attachment.filename
447 assert_equal 'paella.jpg', attachment.filename
448 assert_equal 10790, attachment.filesize
448 assert_equal 10790, attachment.filesize
449 assert File.exist?(attachment.diskfile)
449 assert File.exist?(attachment.diskfile)
450 assert_equal 10790, File.size(attachment.diskfile)
450 assert_equal 10790, File.size(attachment.diskfile)
451 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
451 assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest
452 end
452 end
453
453
454 def test_thunderbird_with_attachment_ja
454 def test_thunderbird_with_attachment_ja
455 issue = submit_email(
455 issue = submit_email(
456 'thunderbird_with_attachment_ja.eml',
456 'thunderbird_with_attachment_ja.eml',
457 :issue => {:project => 'ecookbook'}
457 :issue => {:project => 'ecookbook'}
458 )
458 )
459 assert_kind_of Issue, issue
459 assert_kind_of Issue, issue
460 assert_equal 1, issue.attachments.size
460 assert_equal 1, issue.attachments.size
461 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt".force_encoding('UTF-8')
461 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt".force_encoding('UTF-8')
462 attachment = issue.attachments.first
462 attachment = issue.attachments.first
463 assert_equal ja, attachment.filename
463 assert_equal ja, attachment.filename
464 assert_equal 5, attachment.filesize
464 assert_equal 5, attachment.filesize
465 assert File.exist?(attachment.diskfile)
465 assert File.exist?(attachment.diskfile)
466 assert_equal 5, File.size(attachment.diskfile)
466 assert_equal 5, File.size(attachment.diskfile)
467 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
467 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
468 end
468 end
469
469
470 def test_gmail_with_attachment_ja
470 def test_gmail_with_attachment_ja
471 issue = submit_email(
471 issue = submit_email(
472 'gmail_with_attachment_ja.eml',
472 'gmail_with_attachment_ja.eml',
473 :issue => {:project => 'ecookbook'}
473 :issue => {:project => 'ecookbook'}
474 )
474 )
475 assert_kind_of Issue, issue
475 assert_kind_of Issue, issue
476 assert_equal 1, issue.attachments.size
476 assert_equal 1, issue.attachments.size
477 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt".force_encoding('UTF-8')
477 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt".force_encoding('UTF-8')
478 attachment = issue.attachments.first
478 attachment = issue.attachments.first
479 assert_equal ja, attachment.filename
479 assert_equal ja, attachment.filename
480 assert_equal 5, attachment.filesize
480 assert_equal 5, attachment.filesize
481 assert File.exist?(attachment.diskfile)
481 assert File.exist?(attachment.diskfile)
482 assert_equal 5, File.size(attachment.diskfile)
482 assert_equal 5, File.size(attachment.diskfile)
483 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
483 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
484 end
484 end
485
485
486 def test_thunderbird_with_attachment_latin1
486 def test_thunderbird_with_attachment_latin1
487 issue = submit_email(
487 issue = submit_email(
488 'thunderbird_with_attachment_iso-8859-1.eml',
488 'thunderbird_with_attachment_iso-8859-1.eml',
489 :issue => {:project => 'ecookbook'}
489 :issue => {:project => 'ecookbook'}
490 )
490 )
491 assert_kind_of Issue, issue
491 assert_kind_of Issue, issue
492 assert_equal 1, issue.attachments.size
492 assert_equal 1, issue.attachments.size
493 u = "".force_encoding('UTF-8')
493 u = "".force_encoding('UTF-8')
494 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc".force_encoding('UTF-8')
494 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc".force_encoding('UTF-8')
495 11.times { u << u1 }
495 11.times { u << u1 }
496 attachment = issue.attachments.first
496 attachment = issue.attachments.first
497 assert_equal "#{u}.png", attachment.filename
497 assert_equal "#{u}.png", attachment.filename
498 assert_equal 130, attachment.filesize
498 assert_equal 130, attachment.filesize
499 assert File.exist?(attachment.diskfile)
499 assert File.exist?(attachment.diskfile)
500 assert_equal 130, File.size(attachment.diskfile)
500 assert_equal 130, File.size(attachment.diskfile)
501 assert_equal '4d80e667ac37dddfe05502530f152abb', attachment.digest
501 assert_equal '4d80e667ac37dddfe05502530f152abb', attachment.digest
502 end
502 end
503
503
504 def test_gmail_with_attachment_latin1
504 def test_gmail_with_attachment_latin1
505 issue = submit_email(
505 issue = submit_email(
506 'gmail_with_attachment_iso-8859-1.eml',
506 'gmail_with_attachment_iso-8859-1.eml',
507 :issue => {:project => 'ecookbook'}
507 :issue => {:project => 'ecookbook'}
508 )
508 )
509 assert_kind_of Issue, issue
509 assert_kind_of Issue, issue
510 assert_equal 1, issue.attachments.size
510 assert_equal 1, issue.attachments.size
511 u = "".force_encoding('UTF-8')
511 u = "".force_encoding('UTF-8')
512 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc".force_encoding('UTF-8')
512 u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc".force_encoding('UTF-8')
513 11.times { u << u1 }
513 11.times { u << u1 }
514 attachment = issue.attachments.first
514 attachment = issue.attachments.first
515 assert_equal "#{u}.txt", attachment.filename
515 assert_equal "#{u}.txt", attachment.filename
516 assert_equal 5, attachment.filesize
516 assert_equal 5, attachment.filesize
517 assert File.exist?(attachment.diskfile)
517 assert File.exist?(attachment.diskfile)
518 assert_equal 5, File.size(attachment.diskfile)
518 assert_equal 5, File.size(attachment.diskfile)
519 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
519 assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest
520 end
520 end
521
521
522 def test_multiple_inline_text_parts_should_be_appended_to_issue_description
522 def test_multiple_inline_text_parts_should_be_appended_to_issue_description
523 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
523 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
524 assert_include 'first', issue.description
524 assert_include 'first', issue.description
525 assert_include 'second', issue.description
525 assert_include 'second', issue.description
526 assert_include 'third', issue.description
526 assert_include 'third', issue.description
527 end
527 end
528
528
529 def test_attachment_text_part_should_be_added_as_issue_attachment
529 def test_attachment_text_part_should_be_added_as_issue_attachment
530 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
530 issue = submit_email('multiple_text_parts.eml', :issue => {:project => 'ecookbook'})
531 assert_not_include 'Plain text attachment', issue.description
531 assert_not_include 'Plain text attachment', issue.description
532 attachment = issue.attachments.detect {|a| a.filename == 'textfile.txt'}
532 attachment = issue.attachments.detect {|a| a.filename == 'textfile.txt'}
533 assert_not_nil attachment
533 assert_not_nil attachment
534 assert_include 'Plain text attachment', File.read(attachment.diskfile)
534 assert_include 'Plain text attachment', File.read(attachment.diskfile)
535 end
535 end
536
536
537 def test_add_issue_with_iso_8859_1_subject
537 def test_add_issue_with_iso_8859_1_subject
538 issue = submit_email(
538 issue = submit_email(
539 'subject_as_iso-8859-1.eml',
539 'subject_as_iso-8859-1.eml',
540 :issue => {:project => 'ecookbook'}
540 :issue => {:project => 'ecookbook'}
541 )
541 )
542 str = "Testmail from Webmail: \xc3\xa4 \xc3\xb6 \xc3\xbc...".force_encoding('UTF-8')
542 str = "Testmail from Webmail: \xc3\xa4 \xc3\xb6 \xc3\xbc...".force_encoding('UTF-8')
543 assert_kind_of Issue, issue
543 assert_kind_of Issue, issue
544 assert_equal str, issue.subject
544 assert_equal str, issue.subject
545 end
545 end
546
546
547 def test_quoted_printable_utf8
547 def test_quoted_printable_utf8
548 issue = submit_email(
548 issue = submit_email(
549 'quoted_printable_utf8.eml',
549 'quoted_printable_utf8.eml',
550 :issue => {:project => 'ecookbook'}
550 :issue => {:project => 'ecookbook'}
551 )
551 )
552 assert_kind_of Issue, issue
552 assert_kind_of Issue, issue
553 str = "Freundliche Gr\xc3\xbcsse".force_encoding('UTF-8')
553 str = "Freundliche Gr\xc3\xbcsse".force_encoding('UTF-8')
554 assert_equal str, issue.description
554 assert_equal str, issue.description
555 end
555 end
556
556
557 def test_gmail_iso8859_2
557 def test_gmail_iso8859_2
558 issue = submit_email(
558 issue = submit_email(
559 'gmail-iso8859-2.eml',
559 'gmail-iso8859-2.eml',
560 :issue => {:project => 'ecookbook'}
560 :issue => {:project => 'ecookbook'}
561 )
561 )
562 assert_kind_of Issue, issue
562 assert_kind_of Issue, issue
563 str = "Na \xc5\xa1triku se su\xc5\xa1i \xc5\xa1osi\xc4\x87.".force_encoding('UTF-8')
563 str = "Na \xc5\xa1triku se su\xc5\xa1i \xc5\xa1osi\xc4\x87.".force_encoding('UTF-8')
564 assert issue.description.include?(str)
564 assert issue.description.include?(str)
565 end
565 end
566
566
567 def test_add_issue_with_japanese_subject
567 def test_add_issue_with_japanese_subject
568 issue = submit_email(
568 issue = submit_email(
569 'subject_japanese_1.eml',
569 'subject_japanese_1.eml',
570 :issue => {:project => 'ecookbook'}
570 :issue => {:project => 'ecookbook'}
571 )
571 )
572 assert_kind_of Issue, issue
572 assert_kind_of Issue, issue
573 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88".force_encoding('UTF-8')
573 ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88".force_encoding('UTF-8')
574 assert_equal ja, issue.subject
574 assert_equal ja, issue.subject
575 end
575 end
576
576
577 def test_add_issue_with_korean_body
577 def test_add_issue_with_korean_body
578 # Make sure mail bodies with a charset unknown to Ruby
578 # Make sure mail bodies with a charset unknown to Ruby
579 # but known to the Mail gem 2.5.4 are handled correctly
579 # but known to the Mail gem 2.5.4 are handled correctly
580 kr = "\xEA\xB3\xA0\xEB\xA7\x99\xEC\x8A\xB5\xEB\x8B\x88\xEB\x8B\xA4.".force_encoding('UTF-8')
580 kr = "\xEA\xB3\xA0\xEB\xA7\x99\xEC\x8A\xB5\xEB\x8B\x88\xEB\x8B\xA4.".force_encoding('UTF-8')
581 issue = submit_email(
581 issue = submit_email(
582 'body_ks_c_5601-1987.eml',
582 'body_ks_c_5601-1987.eml',
583 :issue => {:project => 'ecookbook'}
583 :issue => {:project => 'ecookbook'}
584 )
584 )
585 assert_kind_of Issue, issue
585 assert_kind_of Issue, issue
586 assert_equal kr, issue.description
586 assert_equal kr, issue.description
587 end
587 end
588
588
589 def test_add_issue_with_no_subject_header
589 def test_add_issue_with_no_subject_header
590 issue = submit_email(
590 issue = submit_email(
591 'no_subject_header.eml',
591 'no_subject_header.eml',
592 :issue => {:project => 'ecookbook'}
592 :issue => {:project => 'ecookbook'}
593 )
593 )
594 assert_kind_of Issue, issue
594 assert_kind_of Issue, issue
595 assert_equal '(no subject)', issue.subject
595 assert_equal '(no subject)', issue.subject
596 end
596 end
597
597
598 def test_add_issue_with_mixed_japanese_subject
598 def test_add_issue_with_mixed_japanese_subject
599 issue = submit_email(
599 issue = submit_email(
600 'subject_japanese_2.eml',
600 'subject_japanese_2.eml',
601 :issue => {:project => 'ecookbook'}
601 :issue => {:project => 'ecookbook'}
602 )
602 )
603 assert_kind_of Issue, issue
603 assert_kind_of Issue, issue
604 ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88".force_encoding('UTF-8')
604 ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88".force_encoding('UTF-8')
605 assert_equal ja, issue.subject
605 assert_equal ja, issue.subject
606 end
606 end
607
607
608 def test_should_ignore_emails_from_locked_users
608 def test_should_ignore_emails_from_locked_users
609 User.find(2).lock!
609 User.find(2).lock!
610
610
611 MailHandler.any_instance.expects(:dispatch).never
611 MailHandler.any_instance.expects(:dispatch).never
612 assert_no_difference 'Issue.count' do
612 assert_no_difference 'Issue.count' do
613 assert_equal false, submit_email('ticket_on_given_project.eml')
613 assert_equal false, submit_email('ticket_on_given_project.eml')
614 end
614 end
615 end
615 end
616
616
617 def test_should_ignore_emails_from_emission_address
617 def test_should_ignore_emails_from_emission_address
618 Role.anonymous.add_permission!(:add_issues)
618 Role.anonymous.add_permission!(:add_issues)
619 assert_no_difference 'User.count' do
619 assert_no_difference 'User.count' do
620 assert_equal false,
620 assert_equal false,
621 submit_email(
621 submit_email(
622 'ticket_from_emission_address.eml',
622 'ticket_from_emission_address.eml',
623 :issue => {:project => 'ecookbook'},
623 :issue => {:project => 'ecookbook'},
624 :unknown_user => 'create'
624 :unknown_user => 'create'
625 )
625 )
626 end
626 end
627 end
627 end
628
628
629 def test_should_ignore_auto_replied_emails
629 def test_should_ignore_auto_replied_emails
630 MailHandler.any_instance.expects(:dispatch).never
630 MailHandler.any_instance.expects(:dispatch).never
631 [
631 [
632 "X-Auto-Response-Suppress: OOF",
633 "Auto-Submitted: auto-replied",
632 "Auto-Submitted: auto-replied",
634 "Auto-Submitted: Auto-Replied",
633 "Auto-Submitted: Auto-Replied",
635 "Auto-Submitted: auto-generated",
634 "Auto-Submitted: auto-generated",
636 'X-Autoreply: yes'
635 'X-Autoreply: yes'
637 ].each do |header|
636 ].each do |header|
638 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
637 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
639 raw = header + "\n" + raw
638 raw = header + "\n" + raw
640
639
641 assert_no_difference 'Issue.count' do
640 assert_no_difference 'Issue.count' do
642 assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
641 assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
643 end
642 end
644 end
643 end
645 end
644 end
646
645
647 test "should not ignore Auto-Submitted headers not defined in RFC3834" do
646 test "should not ignore Auto-Submitted headers not defined in RFC3834" do
648 [
647 [
649 "Auto-Submitted: auto-forwarded"
648 "Auto-Submitted: auto-forwarded"
650 ].each do |header|
649 ].each do |header|
651 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
650 raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
652 raw = header + "\n" + raw
651 raw = header + "\n" + raw
653
652
654 assert_difference 'Issue.count', 1 do
653 assert_difference 'Issue.count', 1 do
655 assert_not_nil MailHandler.receive(raw), "email with #{header} header was ignored"
654 assert_not_nil MailHandler.receive(raw), "email with #{header} header was ignored"
656 end
655 end
657 end
656 end
658 end
657 end
659
658
660 def test_add_issue_should_send_email_notification
659 def test_add_issue_should_send_email_notification
661 Setting.notified_events = ['issue_added']
660 Setting.notified_events = ['issue_added']
662 ActionMailer::Base.deliveries.clear
661 ActionMailer::Base.deliveries.clear
663 # This email contains: 'Project: onlinestore'
662 # This email contains: 'Project: onlinestore'
664 issue = submit_email('ticket_on_given_project.eml')
663 issue = submit_email('ticket_on_given_project.eml')
665 assert issue.is_a?(Issue)
664 assert issue.is_a?(Issue)
666 assert_equal 1, ActionMailer::Base.deliveries.size
665 assert_equal 1, ActionMailer::Base.deliveries.size
667 end
666 end
668
667
669 def test_update_issue
668 def test_update_issue
670 journal = submit_email('ticket_reply.eml')
669 journal = submit_email('ticket_reply.eml')
671 assert journal.is_a?(Journal)
670 assert journal.is_a?(Journal)
672 assert_equal User.find_by_login('jsmith'), journal.user
671 assert_equal User.find_by_login('jsmith'), journal.user
673 assert_equal Issue.find(2), journal.journalized
672 assert_equal Issue.find(2), journal.journalized
674 assert_match /This is reply/, journal.notes
673 assert_match /This is reply/, journal.notes
675 assert_equal false, journal.private_notes
674 assert_equal false, journal.private_notes
676 assert_equal 'Feature request', journal.issue.tracker.name
675 assert_equal 'Feature request', journal.issue.tracker.name
677 end
676 end
678
677
679 def test_update_issue_should_accept_issue_id_after_space_inside_brackets
678 def test_update_issue_should_accept_issue_id_after_space_inside_brackets
680 journal = submit_email('ticket_reply_with_status.eml') do |email|
679 journal = submit_email('ticket_reply_with_status.eml') do |email|
681 assert email.sub!(/^Subject:.*$/, "Subject: Re: [Feature request #2] Add ingredients categories")
680 assert email.sub!(/^Subject:.*$/, "Subject: Re: [Feature request #2] Add ingredients categories")
682 end
681 end
683 assert journal.is_a?(Journal)
682 assert journal.is_a?(Journal)
684 assert_equal Issue.find(2), journal.journalized
683 assert_equal Issue.find(2), journal.journalized
685 end
684 end
686
685
687 def test_update_issue_should_accept_issue_id_inside_brackets
686 def test_update_issue_should_accept_issue_id_inside_brackets
688 journal = submit_email('ticket_reply_with_status.eml') do |email|
687 journal = submit_email('ticket_reply_with_status.eml') do |email|
689 assert email.sub!(/^Subject:.*$/, "Subject: Re: [#2] Add ingredients categories")
688 assert email.sub!(/^Subject:.*$/, "Subject: Re: [#2] Add ingredients categories")
690 end
689 end
691 assert journal.is_a?(Journal)
690 assert journal.is_a?(Journal)
692 assert_equal Issue.find(2), journal.journalized
691 assert_equal Issue.find(2), journal.journalized
693 end
692 end
694
693
695 def test_update_issue_should_ignore_bogus_issue_ids_in_subject
694 def test_update_issue_should_ignore_bogus_issue_ids_in_subject
696 journal = submit_email('ticket_reply_with_status.eml') do |email|
695 journal = submit_email('ticket_reply_with_status.eml') do |email|
697 assert email.sub!(/^Subject:.*$/, "Subject: Re: [12345#1][bogus#1][Feature request #2] Add ingredients categories")
696 assert email.sub!(/^Subject:.*$/, "Subject: Re: [12345#1][bogus#1][Feature request #2] Add ingredients categories")
698 end
697 end
699 assert journal.is_a?(Journal)
698 assert journal.is_a?(Journal)
700 assert_equal Issue.find(2), journal.journalized
699 assert_equal Issue.find(2), journal.journalized
701 end
700 end
702
701
703 def test_update_issue_with_attribute_changes
702 def test_update_issue_with_attribute_changes
704 # This email contains: 'Status: Resolved'
703 # This email contains: 'Status: Resolved'
705 journal = submit_email('ticket_reply_with_status.eml')
704 journal = submit_email('ticket_reply_with_status.eml')
706 assert journal.is_a?(Journal)
705 assert journal.is_a?(Journal)
707 issue = Issue.find(journal.issue.id)
706 issue = Issue.find(journal.issue.id)
708 assert_equal User.find_by_login('jsmith'), journal.user
707 assert_equal User.find_by_login('jsmith'), journal.user
709 assert_equal Issue.find(2), journal.journalized
708 assert_equal Issue.find(2), journal.journalized
710 assert_match /This is reply/, journal.notes
709 assert_match /This is reply/, journal.notes
711 assert_equal 'Feature request', journal.issue.tracker.name
710 assert_equal 'Feature request', journal.issue.tracker.name
712 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
711 assert_equal IssueStatus.find_by_name("Resolved"), issue.status
713 assert_equal '2010-01-01', issue.start_date.to_s
712 assert_equal '2010-01-01', issue.start_date.to_s
714 assert_equal '2010-12-31', issue.due_date.to_s
713 assert_equal '2010-12-31', issue.due_date.to_s
715 assert_equal User.find_by_login('jsmith'), issue.assigned_to
714 assert_equal User.find_by_login('jsmith'), issue.assigned_to
716 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
715 assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value
717 # keywords should be removed from the email body
716 # keywords should be removed from the email body
718 assert !journal.notes.match(/^Status:/i)
717 assert !journal.notes.match(/^Status:/i)
719 assert !journal.notes.match(/^Start Date:/i)
718 assert !journal.notes.match(/^Start Date:/i)
720 end
719 end
721
720
722 def test_update_issue_with_attachment
721 def test_update_issue_with_attachment
723 assert_difference 'Journal.count' do
722 assert_difference 'Journal.count' do
724 assert_difference 'JournalDetail.count' do
723 assert_difference 'JournalDetail.count' do
725 assert_difference 'Attachment.count' do
724 assert_difference 'Attachment.count' do
726 assert_no_difference 'Issue.count' do
725 assert_no_difference 'Issue.count' do
727 journal = submit_email('ticket_with_attachment.eml') do |raw|
726 journal = submit_email('ticket_with_attachment.eml') do |raw|
728 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
727 raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories'
729 end
728 end
730 end
729 end
731 end
730 end
732 end
731 end
733 end
732 end
734 journal = Journal.order('id DESC').first
733 journal = Journal.order('id DESC').first
735 assert_equal Issue.find(2), journal.journalized
734 assert_equal Issue.find(2), journal.journalized
736 assert_equal 1, journal.details.size
735 assert_equal 1, journal.details.size
737
736
738 detail = journal.details.first
737 detail = journal.details.first
739 assert_equal 'attachment', detail.property
738 assert_equal 'attachment', detail.property
740 assert_equal 'Paella.jpg', detail.value
739 assert_equal 'Paella.jpg', detail.value
741 end
740 end
742
741
743 def test_update_issue_should_send_email_notification
742 def test_update_issue_should_send_email_notification
744 ActionMailer::Base.deliveries.clear
743 ActionMailer::Base.deliveries.clear
745 journal = submit_email('ticket_reply.eml')
744 journal = submit_email('ticket_reply.eml')
746 assert journal.is_a?(Journal)
745 assert journal.is_a?(Journal)
747 assert_equal 1, ActionMailer::Base.deliveries.size
746 assert_equal 1, ActionMailer::Base.deliveries.size
748 end
747 end
749
748
750 def test_update_issue_should_not_set_defaults
749 def test_update_issue_should_not_set_defaults
751 journal = submit_email(
750 journal = submit_email(
752 'ticket_reply.eml',
751 'ticket_reply.eml',
753 :issue => {:tracker => 'Support request', :priority => 'High'}
752 :issue => {:tracker => 'Support request', :priority => 'High'}
754 )
753 )
755 assert journal.is_a?(Journal)
754 assert journal.is_a?(Journal)
756 assert_match /This is reply/, journal.notes
755 assert_match /This is reply/, journal.notes
757 assert_equal 'Feature request', journal.issue.tracker.name
756 assert_equal 'Feature request', journal.issue.tracker.name
758 assert_equal 'Normal', journal.issue.priority.name
757 assert_equal 'Normal', journal.issue.priority.name
759 end
758 end
760
759
761 def test_replying_to_a_private_note_should_add_reply_as_private
760 def test_replying_to_a_private_note_should_add_reply_as_private
762 private_journal = Journal.create!(:notes => 'Private notes', :journalized => Issue.find(1), :private_notes => true, :user_id => 2)
761 private_journal = Journal.create!(:notes => 'Private notes', :journalized => Issue.find(1), :private_notes => true, :user_id => 2)
763
762
764 assert_difference 'Journal.count' do
763 assert_difference 'Journal.count' do
765 journal = submit_email('ticket_reply.eml') do |email|
764 journal = submit_email('ticket_reply.eml') do |email|
766 email.sub! %r{^In-Reply-To:.*$}, "In-Reply-To: <redmine.journal-#{private_journal.id}.20060719210421@osiris>"
765 email.sub! %r{^In-Reply-To:.*$}, "In-Reply-To: <redmine.journal-#{private_journal.id}.20060719210421@osiris>"
767 end
766 end
768
767
769 assert_kind_of Journal, journal
768 assert_kind_of Journal, journal
770 assert_match /This is reply/, journal.notes
769 assert_match /This is reply/, journal.notes
771 assert_equal true, journal.private_notes
770 assert_equal true, journal.private_notes
772 end
771 end
773 end
772 end
774
773
775 def test_reply_to_a_message
774 def test_reply_to_a_message
776 m = submit_email('message_reply.eml')
775 m = submit_email('message_reply.eml')
777 assert m.is_a?(Message)
776 assert m.is_a?(Message)
778 assert !m.new_record?
777 assert !m.new_record?
779 m.reload
778 m.reload
780 assert_equal 'Reply via email', m.subject
779 assert_equal 'Reply via email', m.subject
781 # The email replies to message #2 which is part of the thread of message #1
780 # The email replies to message #2 which is part of the thread of message #1
782 assert_equal Message.find(1), m.parent
781 assert_equal Message.find(1), m.parent
783 end
782 end
784
783
785 def test_reply_to_a_message_by_subject
784 def test_reply_to_a_message_by_subject
786 m = submit_email('message_reply_by_subject.eml')
785 m = submit_email('message_reply_by_subject.eml')
787 assert m.is_a?(Message)
786 assert m.is_a?(Message)
788 assert !m.new_record?
787 assert !m.new_record?
789 m.reload
788 m.reload
790 assert_equal 'Reply to the first post', m.subject
789 assert_equal 'Reply to the first post', m.subject
791 assert_equal Message.find(1), m.parent
790 assert_equal Message.find(1), m.parent
792 end
791 end
793
792
794 def test_should_strip_tags_of_html_only_emails
793 def test_should_strip_tags_of_html_only_emails
795 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
794 issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'})
796 assert issue.is_a?(Issue)
795 assert issue.is_a?(Issue)
797 assert !issue.new_record?
796 assert !issue.new_record?
798 issue.reload
797 issue.reload
799 assert_equal 'HTML email', issue.subject
798 assert_equal 'HTML email', issue.subject
800 assert_equal 'This is a html-only email.', issue.description
799 assert_equal 'This is a html-only email.', issue.description
801 end
800 end
802
801
803 test "truncate emails with no setting should add the entire email into the issue" do
802 test "truncate emails with no setting should add the entire email into the issue" do
804 with_settings :mail_handler_body_delimiters => '' do
803 with_settings :mail_handler_body_delimiters => '' do
805 issue = submit_email('ticket_on_given_project.eml')
804 issue = submit_email('ticket_on_given_project.eml')
806 assert_issue_created(issue)
805 assert_issue_created(issue)
807 assert issue.description.include?('---')
806 assert issue.description.include?('---')
808 assert issue.description.include?('This paragraph is after the delimiter')
807 assert issue.description.include?('This paragraph is after the delimiter')
809 end
808 end
810 end
809 end
811
810
812 test "truncate emails with a single string should truncate the email at the delimiter for the issue" do
811 test "truncate emails with a single string should truncate the email at the delimiter for the issue" do
813 with_settings :mail_handler_body_delimiters => '---' do
812 with_settings :mail_handler_body_delimiters => '---' do
814 issue = submit_email('ticket_on_given_project.eml')
813 issue = submit_email('ticket_on_given_project.eml')
815 assert_issue_created(issue)
814 assert_issue_created(issue)
816 assert issue.description.include?('This paragraph is before delimiters')
815 assert issue.description.include?('This paragraph is before delimiters')
817 assert issue.description.include?('--- This line starts with a delimiter')
816 assert issue.description.include?('--- This line starts with a delimiter')
818 assert !issue.description.match(/^---$/)
817 assert !issue.description.match(/^---$/)
819 assert !issue.description.include?('This paragraph is after the delimiter')
818 assert !issue.description.include?('This paragraph is after the delimiter')
820 end
819 end
821 end
820 end
822
821
823 test "truncate emails with a single quoted reply should truncate the email at the delimiter with the quoted reply symbols (>)" do
822 test "truncate emails with a single quoted reply should truncate the email at the delimiter with the quoted reply symbols (>)" do
824 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
823 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
825 journal = submit_email('issue_update_with_quoted_reply_above.eml')
824 journal = submit_email('issue_update_with_quoted_reply_above.eml')
826 assert journal.is_a?(Journal)
825 assert journal.is_a?(Journal)
827 assert journal.notes.include?('An update to the issue by the sender.')
826 assert journal.notes.include?('An update to the issue by the sender.')
828 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
827 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
829 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
828 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
830 end
829 end
831 end
830 end
832
831
833 test "truncate emails with multiple quoted replies should truncate the email at the delimiter with the quoted reply symbols (>)" do
832 test "truncate emails with multiple quoted replies should truncate the email at the delimiter with the quoted reply symbols (>)" do
834 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
833 with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
835 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
834 journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
836 assert journal.is_a?(Journal)
835 assert journal.is_a?(Journal)
837 assert journal.notes.include?('An update to the issue by the sender.')
836 assert journal.notes.include?('An update to the issue by the sender.')
838 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
837 assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
839 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
838 assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
840 end
839 end
841 end
840 end
842
841
843 test "truncate emails with multiple strings should truncate the email at the first delimiter found (BREAK)" do
842 test "truncate emails with multiple strings should truncate the email at the first delimiter found (BREAK)" do
844 with_settings :mail_handler_body_delimiters => "---\nBREAK" do
843 with_settings :mail_handler_body_delimiters => "---\nBREAK" do
845 issue = submit_email('ticket_on_given_project.eml')
844 issue = submit_email('ticket_on_given_project.eml')
846 assert_issue_created(issue)
845 assert_issue_created(issue)
847 assert issue.description.include?('This paragraph is before delimiters')
846 assert issue.description.include?('This paragraph is before delimiters')
848 assert !issue.description.include?('BREAK')
847 assert !issue.description.include?('BREAK')
849 assert !issue.description.include?('This paragraph is between delimiters')
848 assert !issue.description.include?('This paragraph is between delimiters')
850 assert !issue.description.match(/^---$/)
849 assert !issue.description.match(/^---$/)
851 assert !issue.description.include?('This paragraph is after the delimiter')
850 assert !issue.description.include?('This paragraph is after the delimiter')
852 end
851 end
853 end
852 end
854
853
855 def test_attachments_that_match_mail_handler_excluded_filenames_should_be_ignored
854 def test_attachments_that_match_mail_handler_excluded_filenames_should_be_ignored
856 with_settings :mail_handler_excluded_filenames => '*.vcf, *.jpg' do
855 with_settings :mail_handler_excluded_filenames => '*.vcf, *.jpg' do
857 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
856 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
858 assert issue.is_a?(Issue)
857 assert issue.is_a?(Issue)
859 assert !issue.new_record?
858 assert !issue.new_record?
860 assert_equal 0, issue.reload.attachments.size
859 assert_equal 0, issue.reload.attachments.size
861 end
860 end
862 end
861 end
863
862
864 def test_attachments_that_do_not_match_mail_handler_excluded_filenames_should_be_attached
863 def test_attachments_that_do_not_match_mail_handler_excluded_filenames_should_be_attached
865 with_settings :mail_handler_excluded_filenames => '*.vcf, *.gif' do
864 with_settings :mail_handler_excluded_filenames => '*.vcf, *.gif' do
866 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
865 issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'})
867 assert issue.is_a?(Issue)
866 assert issue.is_a?(Issue)
868 assert !issue.new_record?
867 assert !issue.new_record?
869 assert_equal 1, issue.reload.attachments.size
868 assert_equal 1, issue.reload.attachments.size
870 end
869 end
871 end
870 end
872
871
873 def test_email_with_long_subject_line
872 def test_email_with_long_subject_line
874 issue = submit_email('ticket_with_long_subject.eml')
873 issue = submit_email('ticket_with_long_subject.eml')
875 assert issue.is_a?(Issue)
874 assert issue.is_a?(Issue)
876 assert_equal issue.subject, 'New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say...'[0,255]
875 assert_equal issue.subject, 'New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say...'[0,255]
877 end
876 end
878
877
879 def test_first_keyword_should_be_matched
878 def test_first_keyword_should_be_matched
880 issue = submit_email('ticket_with_duplicate_keyword.eml', :allow_override => 'priority')
879 issue = submit_email('ticket_with_duplicate_keyword.eml', :allow_override => 'priority')
881 assert issue.is_a?(Issue)
880 assert issue.is_a?(Issue)
882 assert_equal 'High', issue.priority.name
881 assert_equal 'High', issue.priority.name
883 end
882 end
884
883
885 def test_keyword_after_delimiter_should_be_ignored
884 def test_keyword_after_delimiter_should_be_ignored
886 with_settings :mail_handler_body_delimiters => "== DELIMITER ==" do
885 with_settings :mail_handler_body_delimiters => "== DELIMITER ==" do
887 issue = submit_email('ticket_with_keyword_after_delimiter.eml', :allow_override => 'priority')
886 issue = submit_email('ticket_with_keyword_after_delimiter.eml', :allow_override => 'priority')
888 assert issue.is_a?(Issue)
887 assert issue.is_a?(Issue)
889 assert_equal 'Normal', issue.priority.name
888 assert_equal 'Normal', issue.priority.name
890 end
889 end
891 end
890 end
892
891
893 def test_new_user_from_attributes_should_return_valid_user
892 def test_new_user_from_attributes_should_return_valid_user
894 to_test = {
893 to_test = {
895 # [address, name] => [login, firstname, lastname]
894 # [address, name] => [login, firstname, lastname]
896 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
895 ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'],
897 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
896 ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'],
898 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
897 ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'],
899 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
898 ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'],
900 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
899 ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'],
901 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
900 ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh']
902 }
901 }
903
902
904 to_test.each do |attrs, expected|
903 to_test.each do |attrs, expected|
905 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
904 user = MailHandler.new_user_from_attributes(attrs.first, attrs.last)
906
905
907 assert user.valid?, user.errors.full_messages.to_s
906 assert user.valid?, user.errors.full_messages.to_s
908 assert_equal attrs.first, user.mail
907 assert_equal attrs.first, user.mail
909 assert_equal expected[0], user.login
908 assert_equal expected[0], user.login
910 assert_equal expected[1], user.firstname
909 assert_equal expected[1], user.firstname
911 assert_equal expected[2], user.lastname
910 assert_equal expected[2], user.lastname
912 assert_equal 'only_my_events', user.mail_notification
911 assert_equal 'only_my_events', user.mail_notification
913 end
912 end
914 end
913 end
915
914
916 def test_new_user_from_attributes_should_use_default_login_if_invalid
915 def test_new_user_from_attributes_should_use_default_login_if_invalid
917 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
916 user = MailHandler.new_user_from_attributes('foo+bar@example.net')
918 assert user.valid?
917 assert user.valid?
919 assert user.login =~ /^user[a-f0-9]+$/
918 assert user.login =~ /^user[a-f0-9]+$/
920 assert_equal 'foo+bar@example.net', user.mail
919 assert_equal 'foo+bar@example.net', user.mail
921 end
920 end
922
921
923 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
922 def test_new_user_with_utf8_encoded_fullname_should_be_decoded
924 assert_difference 'User.count' do
923 assert_difference 'User.count' do
925 issue = submit_email(
924 issue = submit_email(
926 'fullname_of_sender_as_utf8_encoded.eml',
925 'fullname_of_sender_as_utf8_encoded.eml',
927 :issue => {:project => 'ecookbook'},
926 :issue => {:project => 'ecookbook'},
928 :unknown_user => 'create'
927 :unknown_user => 'create'
929 )
928 )
930 end
929 end
931 user = User.order('id DESC').first
930 user = User.order('id DESC').first
932 assert_equal "foo@example.org", user.mail
931 assert_equal "foo@example.org", user.mail
933 str1 = "\xc3\x84\xc3\xa4".force_encoding('UTF-8')
932 str1 = "\xc3\x84\xc3\xa4".force_encoding('UTF-8')
934 str2 = "\xc3\x96\xc3\xb6".force_encoding('UTF-8')
933 str2 = "\xc3\x96\xc3\xb6".force_encoding('UTF-8')
935 assert_equal str1, user.firstname
934 assert_equal str1, user.firstname
936 assert_equal str2, user.lastname
935 assert_equal str2, user.lastname
937 end
936 end
938
937
939 def test_extract_options_from_env_should_return_options
938 def test_extract_options_from_env_should_return_options
940 options = MailHandler.extract_options_from_env({
939 options = MailHandler.extract_options_from_env({
941 'tracker' => 'defect',
940 'tracker' => 'defect',
942 'project' => 'foo',
941 'project' => 'foo',
943 'unknown_user' => 'create'
942 'unknown_user' => 'create'
944 })
943 })
945
944
946 assert_equal({
945 assert_equal({
947 :issue => {:tracker => 'defect', :project => 'foo'},
946 :issue => {:tracker => 'defect', :project => 'foo'},
948 :unknown_user => 'create'
947 :unknown_user => 'create'
949 }, options)
948 }, options)
950 end
949 end
951
950
952 def test_safe_receive_should_rescue_exceptions_and_return_false
951 def test_safe_receive_should_rescue_exceptions_and_return_false
953 MailHandler.stubs(:receive).raises(Exception.new "Something went wrong")
952 MailHandler.stubs(:receive).raises(Exception.new "Something went wrong")
954
953
955 assert_equal false, MailHandler.safe_receive
954 assert_equal false, MailHandler.safe_receive
956 end
955 end
957
956
958 private
957 private
959
958
960 def submit_email(filename, options={})
959 def submit_email(filename, options={})
961 raw = IO.read(File.join(FIXTURES_PATH, filename))
960 raw = IO.read(File.join(FIXTURES_PATH, filename))
962 yield raw if block_given?
961 yield raw if block_given?
963 MailHandler.receive(raw, options)
962 MailHandler.receive(raw, options)
964 end
963 end
965
964
966 def assert_issue_created(issue)
965 def assert_issue_created(issue)
967 assert issue.is_a?(Issue)
966 assert issue.is_a?(Issue)
968 assert !issue.new_record?
967 assert !issue.new_record?
969 issue.reload
968 issue.reload
970 end
969 end
971 end
970 end
@@ -1,853 +1,853
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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 Rails::Dom::Testing::Assertions
22 include Rails::Dom::Testing::Assertions
23 fixtures :projects, :enabled_modules, :issues, :users, :email_addresses, :members,
23 fixtures :projects, :enabled_modules, :issues, :users, :email_addresses, :members,
24 :member_roles, :roles, :documents, :attachments, :news,
24 :member_roles, :roles, :documents, :attachments, :news,
25 :tokens, :journals, :journal_details, :changesets,
25 :tokens, :journals, :journal_details, :changesets,
26 :trackers, :projects_trackers,
26 :trackers, :projects_trackers,
27 :issue_statuses, :enumerations, :messages, :boards, :repositories,
27 :issue_statuses, :enumerations, :messages, :boards, :repositories,
28 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
28 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
29 :versions,
29 :versions,
30 :comments
30 :comments
31
31
32 def setup
32 def setup
33 ActionMailer::Base.deliveries.clear
33 ActionMailer::Base.deliveries.clear
34 Setting.host_name = 'mydomain.foo'
34 Setting.host_name = 'mydomain.foo'
35 Setting.protocol = 'http'
35 Setting.protocol = 'http'
36 Setting.plain_text_mail = '0'
36 Setting.plain_text_mail = '0'
37 Setting.default_language = 'en'
37 Setting.default_language = 'en'
38 User.current = nil
38 User.current = nil
39 end
39 end
40
40
41 def test_generated_links_in_emails
41 def test_generated_links_in_emails
42 Setting.host_name = 'mydomain.foo'
42 Setting.host_name = 'mydomain.foo'
43 Setting.protocol = 'https'
43 Setting.protocol = 'https'
44
44
45 journal = Journal.find(3)
45 journal = Journal.find(3)
46 assert Mailer.deliver_issue_edit(journal)
46 assert Mailer.deliver_issue_edit(journal)
47
47
48 mail = last_email
48 mail = last_email
49 assert_not_nil mail
49 assert_not_nil mail
50
50
51 assert_select_email do
51 assert_select_email do
52 # link to the main ticket
52 # link to the main ticket
53 assert_select 'a[href=?]',
53 assert_select 'a[href=?]',
54 'https://mydomain.foo/issues/2#change-3',
54 'https://mydomain.foo/issues/2#change-3',
55 :text => 'Feature request #2: Add ingredients categories'
55 :text => 'Feature request #2: Add ingredients categories'
56 # link to a referenced ticket
56 # link to a referenced ticket
57 assert_select 'a[href=?][title=?]',
57 assert_select 'a[href=?][title=?]',
58 'https://mydomain.foo/issues/1',
58 'https://mydomain.foo/issues/1',
59 "Cannot print recipes (New)",
59 "Cannot print recipes (New)",
60 :text => '#1'
60 :text => '#1'
61 # link to a changeset
61 # link to a changeset
62 assert_select 'a[href=?][title=?]',
62 assert_select 'a[href=?][title=?]',
63 'https://mydomain.foo/projects/ecookbook/repository/revisions/2',
63 'https://mydomain.foo/projects/ecookbook/repository/revisions/2',
64 'This commit fixes #1, #2 and references #1 & #3',
64 'This commit fixes #1, #2 and references #1 & #3',
65 :text => 'r2'
65 :text => 'r2'
66 # link to a description diff
66 # link to a description diff
67 assert_select 'a[href^=?][title=?]',
67 assert_select 'a[href^=?][title=?]',
68 # should be https://mydomain.foo/journals/diff/3?detail_id=4
68 # should be https://mydomain.foo/journals/diff/3?detail_id=4
69 # but the Rails 4.2 DOM assertion doesn't handle the ? in the
69 # but the Rails 4.2 DOM assertion doesn't handle the ? in the
70 # attribute value
70 # attribute value
71 'https://mydomain.foo/journals/diff/3',
71 'https://mydomain.foo/journals/diff/3',
72 'View differences',
72 'View differences',
73 :text => 'diff'
73 :text => 'diff'
74 # link to an attachment
74 # link to an attachment
75 assert_select 'a[href=?]',
75 assert_select 'a[href=?]',
76 'https://mydomain.foo/attachments/download/4/source.rb',
76 'https://mydomain.foo/attachments/download/4/source.rb',
77 :text => 'source.rb'
77 :text => 'source.rb'
78 end
78 end
79 end
79 end
80
80
81 def test_generated_links_with_prefix
81 def test_generated_links_with_prefix
82 relative_url_root = Redmine::Utils.relative_url_root
82 relative_url_root = Redmine::Utils.relative_url_root
83 Setting.host_name = 'mydomain.foo/rdm'
83 Setting.host_name = 'mydomain.foo/rdm'
84 Setting.protocol = 'http'
84 Setting.protocol = 'http'
85
85
86 journal = Journal.find(3)
86 journal = Journal.find(3)
87 assert Mailer.deliver_issue_edit(journal)
87 assert Mailer.deliver_issue_edit(journal)
88
88
89 mail = last_email
89 mail = last_email
90 assert_not_nil mail
90 assert_not_nil mail
91
91
92 assert_select_email do
92 assert_select_email do
93 # link to the main ticket
93 # link to the main ticket
94 assert_select 'a[href=?]',
94 assert_select 'a[href=?]',
95 'http://mydomain.foo/rdm/issues/2#change-3',
95 'http://mydomain.foo/rdm/issues/2#change-3',
96 :text => 'Feature request #2: Add ingredients categories'
96 :text => 'Feature request #2: Add ingredients categories'
97 # link to a referenced ticket
97 # link to a referenced ticket
98 assert_select 'a[href=?][title=?]',
98 assert_select 'a[href=?][title=?]',
99 'http://mydomain.foo/rdm/issues/1',
99 'http://mydomain.foo/rdm/issues/1',
100 "Cannot print recipes (New)",
100 "Cannot print recipes (New)",
101 :text => '#1'
101 :text => '#1'
102 # link to a changeset
102 # link to a changeset
103 assert_select 'a[href=?][title=?]',
103 assert_select 'a[href=?][title=?]',
104 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2',
104 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2',
105 'This commit fixes #1, #2 and references #1 & #3',
105 'This commit fixes #1, #2 and references #1 & #3',
106 :text => 'r2'
106 :text => 'r2'
107 # link to a description diff
107 # link to a description diff
108 assert_select 'a[href^=?][title=?]',
108 assert_select 'a[href^=?][title=?]',
109 # should be http://mydomain.foo/rdm/journals/diff/3?detail_id=4
109 # should be http://mydomain.foo/rdm/journals/diff/3?detail_id=4
110 # but the Rails 4.2 DOM assertion doesn't handle the ? in the
110 # but the Rails 4.2 DOM assertion doesn't handle the ? in the
111 # attribute value
111 # attribute value
112 'http://mydomain.foo/rdm/journals/diff/3',
112 'http://mydomain.foo/rdm/journals/diff/3',
113 'View differences',
113 'View differences',
114 :text => 'diff'
114 :text => 'diff'
115 # link to an attachment
115 # link to an attachment
116 assert_select 'a[href=?]',
116 assert_select 'a[href=?]',
117 'http://mydomain.foo/rdm/attachments/download/4/source.rb',
117 'http://mydomain.foo/rdm/attachments/download/4/source.rb',
118 :text => 'source.rb'
118 :text => 'source.rb'
119 end
119 end
120 end
120 end
121
121
122 def test_generated_links_with_port_and_prefix
122 def test_generated_links_with_port_and_prefix
123 with_settings :host_name => '10.0.0.1:81/redmine', :protocol => 'http' do
123 with_settings :host_name => '10.0.0.1:81/redmine', :protocol => 'http' do
124 Mailer.test_email(User.find(1)).deliver
124 Mailer.test_email(User.find(1)).deliver
125 mail = last_email
125 mail = last_email
126 assert_not_nil mail
126 assert_not_nil mail
127 assert_include 'http://10.0.0.1:81/redmine', mail_body(mail)
127 assert_include 'http://10.0.0.1:81/redmine', mail_body(mail)
128 end
128 end
129 end
129 end
130
130
131 def test_generated_links_with_port
131 def test_generated_links_with_port
132 with_settings :host_name => '10.0.0.1:81', :protocol => 'http' do
132 with_settings :host_name => '10.0.0.1:81', :protocol => 'http' do
133 Mailer.test_email(User.find(1)).deliver
133 Mailer.test_email(User.find(1)).deliver
134 mail = last_email
134 mail = last_email
135 assert_not_nil mail
135 assert_not_nil mail
136 assert_include 'http://10.0.0.1:81', mail_body(mail)
136 assert_include 'http://10.0.0.1:81', mail_body(mail)
137 end
137 end
138 end
138 end
139
139
140 def test_issue_edit_should_generate_url_with_hostname_for_relations
140 def test_issue_edit_should_generate_url_with_hostname_for_relations
141 journal = Journal.new(:journalized => Issue.find(1), :user => User.find(1), :created_on => Time.now)
141 journal = Journal.new(:journalized => Issue.find(1), :user => User.find(1), :created_on => Time.now)
142 journal.details << JournalDetail.new(:property => 'relation', :prop_key => 'label_relates_to', :value => 2)
142 journal.details << JournalDetail.new(:property => 'relation', :prop_key => 'label_relates_to', :value => 2)
143 Mailer.deliver_issue_edit(journal)
143 Mailer.deliver_issue_edit(journal)
144 assert_not_nil last_email
144 assert_not_nil last_email
145 assert_select_email do
145 assert_select_email do
146 assert_select 'a[href=?]', 'http://mydomain.foo/issues/2', :text => 'Feature request #2'
146 assert_select 'a[href=?]', 'http://mydomain.foo/issues/2', :text => 'Feature request #2'
147 end
147 end
148 end
148 end
149
149
150 def test_generated_links_with_prefix_and_no_relative_url_root
150 def test_generated_links_with_prefix_and_no_relative_url_root
151 relative_url_root = Redmine::Utils.relative_url_root
151 relative_url_root = Redmine::Utils.relative_url_root
152 Setting.host_name = 'mydomain.foo/rdm'
152 Setting.host_name = 'mydomain.foo/rdm'
153 Setting.protocol = 'http'
153 Setting.protocol = 'http'
154 Redmine::Utils.relative_url_root = nil
154 Redmine::Utils.relative_url_root = nil
155
155
156 journal = Journal.find(3)
156 journal = Journal.find(3)
157 assert Mailer.deliver_issue_edit(journal)
157 assert Mailer.deliver_issue_edit(journal)
158
158
159 mail = last_email
159 mail = last_email
160 assert_not_nil mail
160 assert_not_nil mail
161
161
162 assert_select_email do
162 assert_select_email do
163 # link to the main ticket
163 # link to the main ticket
164 assert_select 'a[href=?]',
164 assert_select 'a[href=?]',
165 'http://mydomain.foo/rdm/issues/2#change-3',
165 'http://mydomain.foo/rdm/issues/2#change-3',
166 :text => 'Feature request #2: Add ingredients categories'
166 :text => 'Feature request #2: Add ingredients categories'
167 # link to a referenced ticket
167 # link to a referenced ticket
168 assert_select 'a[href=?][title=?]',
168 assert_select 'a[href=?][title=?]',
169 'http://mydomain.foo/rdm/issues/1',
169 'http://mydomain.foo/rdm/issues/1',
170 "Cannot print recipes (New)",
170 "Cannot print recipes (New)",
171 :text => '#1'
171 :text => '#1'
172 # link to a changeset
172 # link to a changeset
173 assert_select 'a[href=?][title=?]',
173 assert_select 'a[href=?][title=?]',
174 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2',
174 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2',
175 'This commit fixes #1, #2 and references #1 & #3',
175 'This commit fixes #1, #2 and references #1 & #3',
176 :text => 'r2'
176 :text => 'r2'
177 # link to a description diff
177 # link to a description diff
178 assert_select 'a[href^=?][title=?]',
178 assert_select 'a[href^=?][title=?]',
179 # should be http://mydomain.foo/rdm/journals/diff/3?detail_id=4
179 # should be http://mydomain.foo/rdm/journals/diff/3?detail_id=4
180 # but the Rails 4.2 DOM assertion doesn't handle the ? in the
180 # but the Rails 4.2 DOM assertion doesn't handle the ? in the
181 # attribute value
181 # attribute value
182 'http://mydomain.foo/rdm/journals/diff/3',
182 'http://mydomain.foo/rdm/journals/diff/3',
183 'View differences',
183 'View differences',
184 :text => 'diff'
184 :text => 'diff'
185 # link to an attachment
185 # link to an attachment
186 assert_select 'a[href=?]',
186 assert_select 'a[href=?]',
187 'http://mydomain.foo/rdm/attachments/download/4/source.rb',
187 'http://mydomain.foo/rdm/attachments/download/4/source.rb',
188 :text => 'source.rb'
188 :text => 'source.rb'
189 end
189 end
190 ensure
190 ensure
191 # restore it
191 # restore it
192 Redmine::Utils.relative_url_root = relative_url_root
192 Redmine::Utils.relative_url_root = relative_url_root
193 end
193 end
194
194
195 def test_email_headers
195 def test_email_headers
196 issue = Issue.find(1)
196 issue = Issue.find(1)
197 Mailer.deliver_issue_add(issue)
197 Mailer.deliver_issue_add(issue)
198 mail = last_email
198 mail = last_email
199 assert_not_nil mail
199 assert_not_nil mail
200 assert_equal 'OOF', mail.header['X-Auto-Response-Suppress'].to_s
200 assert_equal 'All', mail.header['X-Auto-Response-Suppress'].to_s
201 assert_equal 'auto-generated', mail.header['Auto-Submitted'].to_s
201 assert_equal 'auto-generated', mail.header['Auto-Submitted'].to_s
202 assert_equal '<redmine.example.net>', mail.header['List-Id'].to_s
202 assert_equal '<redmine.example.net>', mail.header['List-Id'].to_s
203 end
203 end
204
204
205 def test_email_headers_should_include_sender
205 def test_email_headers_should_include_sender
206 issue = Issue.find(1)
206 issue = Issue.find(1)
207 Mailer.deliver_issue_add(issue)
207 Mailer.deliver_issue_add(issue)
208 mail = last_email
208 mail = last_email
209 assert_equal issue.author.login, mail.header['X-Redmine-Sender'].to_s
209 assert_equal issue.author.login, mail.header['X-Redmine-Sender'].to_s
210 end
210 end
211
211
212 def test_plain_text_mail
212 def test_plain_text_mail
213 Setting.plain_text_mail = 1
213 Setting.plain_text_mail = 1
214 journal = Journal.find(2)
214 journal = Journal.find(2)
215 Mailer.deliver_issue_edit(journal)
215 Mailer.deliver_issue_edit(journal)
216 mail = last_email
216 mail = last_email
217 assert_equal "text/plain; charset=UTF-8", mail.content_type
217 assert_equal "text/plain; charset=UTF-8", mail.content_type
218 assert_equal 0, mail.parts.size
218 assert_equal 0, mail.parts.size
219 assert !mail.encoded.include?('href')
219 assert !mail.encoded.include?('href')
220 end
220 end
221
221
222 def test_html_mail
222 def test_html_mail
223 Setting.plain_text_mail = 0
223 Setting.plain_text_mail = 0
224 journal = Journal.find(2)
224 journal = Journal.find(2)
225 Mailer.deliver_issue_edit(journal)
225 Mailer.deliver_issue_edit(journal)
226 mail = last_email
226 mail = last_email
227 assert_equal 2, mail.parts.size
227 assert_equal 2, mail.parts.size
228 assert mail.encoded.include?('href')
228 assert mail.encoded.include?('href')
229 end
229 end
230
230
231 def test_from_header
231 def test_from_header
232 with_settings :mail_from => 'redmine@example.net' do
232 with_settings :mail_from => 'redmine@example.net' do
233 Mailer.test_email(User.find(1)).deliver
233 Mailer.test_email(User.find(1)).deliver
234 end
234 end
235 mail = last_email
235 mail = last_email
236 assert_equal 'redmine@example.net', mail.from_addrs.first
236 assert_equal 'redmine@example.net', mail.from_addrs.first
237 end
237 end
238
238
239 def test_from_header_with_phrase
239 def test_from_header_with_phrase
240 with_settings :mail_from => 'Redmine app <redmine@example.net>' do
240 with_settings :mail_from => 'Redmine app <redmine@example.net>' do
241 Mailer.test_email(User.find(1)).deliver
241 Mailer.test_email(User.find(1)).deliver
242 end
242 end
243 mail = last_email
243 mail = last_email
244 assert_equal 'redmine@example.net', mail.from_addrs.first
244 assert_equal 'redmine@example.net', mail.from_addrs.first
245 assert_equal 'Redmine app <redmine@example.net>', mail.header['From'].to_s
245 assert_equal 'Redmine app <redmine@example.net>', mail.header['From'].to_s
246 end
246 end
247
247
248 def test_should_not_send_email_without_recipient
248 def test_should_not_send_email_without_recipient
249 news = News.first
249 news = News.first
250 user = news.author
250 user = news.author
251 # Remove members except news author
251 # Remove members except news author
252 news.project.memberships.each {|m| m.destroy unless m.user == user}
252 news.project.memberships.each {|m| m.destroy unless m.user == user}
253
253
254 user.pref.no_self_notified = false
254 user.pref.no_self_notified = false
255 user.pref.save
255 user.pref.save
256 User.current = user
256 User.current = user
257 Mailer.news_added(news.reload).deliver
257 Mailer.news_added(news.reload).deliver
258 assert_equal 1, last_email.bcc.size
258 assert_equal 1, last_email.bcc.size
259
259
260 # nobody to notify
260 # nobody to notify
261 user.pref.no_self_notified = true
261 user.pref.no_self_notified = true
262 user.pref.save
262 user.pref.save
263 User.current = user
263 User.current = user
264 ActionMailer::Base.deliveries.clear
264 ActionMailer::Base.deliveries.clear
265 Mailer.news_added(news.reload).deliver
265 Mailer.news_added(news.reload).deliver
266 assert ActionMailer::Base.deliveries.empty?
266 assert ActionMailer::Base.deliveries.empty?
267 end
267 end
268
268
269 def test_issue_add_message_id
269 def test_issue_add_message_id
270 issue = Issue.find(2)
270 issue = Issue.find(2)
271 Mailer.deliver_issue_add(issue)
271 Mailer.deliver_issue_add(issue)
272 mail = last_email
272 mail = last_email
273 assert_match /^redmine\.issue-2\.20060719190421\.[a-f0-9]+@example\.net/, mail.message_id
273 assert_match /^redmine\.issue-2\.20060719190421\.[a-f0-9]+@example\.net/, mail.message_id
274 assert_include "redmine.issue-2.20060719190421@example.net", mail.references
274 assert_include "redmine.issue-2.20060719190421@example.net", mail.references
275 end
275 end
276
276
277 def test_issue_edit_message_id
277 def test_issue_edit_message_id
278 journal = Journal.find(3)
278 journal = Journal.find(3)
279 journal.issue = Issue.find(2)
279 journal.issue = Issue.find(2)
280
280
281 Mailer.deliver_issue_edit(journal)
281 Mailer.deliver_issue_edit(journal)
282 mail = last_email
282 mail = last_email
283 assert_match /^redmine\.journal-3\.\d+\.[a-f0-9]+@example\.net/, mail.message_id
283 assert_match /^redmine\.journal-3\.\d+\.[a-f0-9]+@example\.net/, mail.message_id
284 assert_include "redmine.issue-2.20060719190421@example.net", mail.references
284 assert_include "redmine.issue-2.20060719190421@example.net", mail.references
285 assert_select_email do
285 assert_select_email do
286 # link to the update
286 # link to the update
287 assert_select "a[href=?]",
287 assert_select "a[href=?]",
288 "http://mydomain.foo/issues/#{journal.journalized_id}#change-#{journal.id}"
288 "http://mydomain.foo/issues/#{journal.journalized_id}#change-#{journal.id}"
289 end
289 end
290 end
290 end
291
291
292 def test_message_posted_message_id
292 def test_message_posted_message_id
293 message = Message.find(1)
293 message = Message.find(1)
294 Mailer.message_posted(message).deliver
294 Mailer.message_posted(message).deliver
295 mail = last_email
295 mail = last_email
296 assert_match /^redmine\.message-1\.\d+\.[a-f0-9]+@example\.net/, mail.message_id
296 assert_match /^redmine\.message-1\.\d+\.[a-f0-9]+@example\.net/, mail.message_id
297 assert_include "redmine.message-1.20070512151532@example.net", mail.references
297 assert_include "redmine.message-1.20070512151532@example.net", mail.references
298 assert_select_email do
298 assert_select_email do
299 # link to the message
299 # link to the message
300 assert_select "a[href=?]",
300 assert_select "a[href=?]",
301 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}",
301 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}",
302 :text => message.subject
302 :text => message.subject
303 end
303 end
304 end
304 end
305
305
306 def test_reply_posted_message_id
306 def test_reply_posted_message_id
307 message = Message.find(3)
307 message = Message.find(3)
308 Mailer.message_posted(message).deliver
308 Mailer.message_posted(message).deliver
309 mail = last_email
309 mail = last_email
310 assert_match /^redmine\.message-3\.\d+\.[a-f0-9]+@example\.net/, mail.message_id
310 assert_match /^redmine\.message-3\.\d+\.[a-f0-9]+@example\.net/, mail.message_id
311 assert_include "redmine.message-1.20070512151532@example.net", mail.references
311 assert_include "redmine.message-1.20070512151532@example.net", mail.references
312 assert_select_email do
312 assert_select_email do
313 # link to the reply
313 # link to the reply
314 assert_select "a[href=?]",
314 assert_select "a[href=?]",
315 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.root.id}?r=#{message.id}#message-#{message.id}",
315 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.root.id}?r=#{message.id}#message-#{message.id}",
316 :text => message.subject
316 :text => message.subject
317 end
317 end
318 end
318 end
319
319
320 test "#issue_add should notify project members" do
320 test "#issue_add should notify project members" do
321 issue = Issue.find(1)
321 issue = Issue.find(1)
322 assert Mailer.deliver_issue_add(issue)
322 assert Mailer.deliver_issue_add(issue)
323 assert last_email.bcc.include?('dlopper@somenet.foo')
323 assert last_email.bcc.include?('dlopper@somenet.foo')
324 end
324 end
325
325
326 def test_issue_add_should_send_mail_to_all_user_email_address
326 def test_issue_add_should_send_mail_to_all_user_email_address
327 EmailAddress.create!(:user_id => 3, :address => 'otheremail@somenet.foo')
327 EmailAddress.create!(:user_id => 3, :address => 'otheremail@somenet.foo')
328 issue = Issue.find(1)
328 issue = Issue.find(1)
329 assert Mailer.deliver_issue_add(issue)
329 assert Mailer.deliver_issue_add(issue)
330 assert last_email.bcc.include?('dlopper@somenet.foo')
330 assert last_email.bcc.include?('dlopper@somenet.foo')
331 assert last_email.bcc.include?('otheremail@somenet.foo')
331 assert last_email.bcc.include?('otheremail@somenet.foo')
332 end
332 end
333
333
334 test "#issue_add should not notify project members that are not allow to view the issue" do
334 test "#issue_add should not notify project members that are not allow to view the issue" do
335 issue = Issue.find(1)
335 issue = Issue.find(1)
336 Role.find(2).remove_permission!(:view_issues)
336 Role.find(2).remove_permission!(:view_issues)
337 assert Mailer.deliver_issue_add(issue)
337 assert Mailer.deliver_issue_add(issue)
338 assert !last_email.bcc.include?('dlopper@somenet.foo')
338 assert !last_email.bcc.include?('dlopper@somenet.foo')
339 end
339 end
340
340
341 test "#issue_add should notify issue watchers" do
341 test "#issue_add should notify issue watchers" do
342 issue = Issue.find(1)
342 issue = Issue.find(1)
343 user = User.find(9)
343 user = User.find(9)
344 # minimal email notification options
344 # minimal email notification options
345 user.pref.no_self_notified = '1'
345 user.pref.no_self_notified = '1'
346 user.pref.save
346 user.pref.save
347 user.mail_notification = false
347 user.mail_notification = false
348 user.save
348 user.save
349
349
350 Watcher.create!(:watchable => issue, :user => user)
350 Watcher.create!(:watchable => issue, :user => user)
351 assert Mailer.deliver_issue_add(issue)
351 assert Mailer.deliver_issue_add(issue)
352 assert last_email.bcc.include?(user.mail)
352 assert last_email.bcc.include?(user.mail)
353 end
353 end
354
354
355 test "#issue_add should not notify watchers not allowed to view the issue" do
355 test "#issue_add should not notify watchers not allowed to view the issue" do
356 issue = Issue.find(1)
356 issue = Issue.find(1)
357 user = User.find(9)
357 user = User.find(9)
358 Watcher.create!(:watchable => issue, :user => user)
358 Watcher.create!(:watchable => issue, :user => user)
359 Role.non_member.remove_permission!(:view_issues)
359 Role.non_member.remove_permission!(:view_issues)
360 assert Mailer.deliver_issue_add(issue)
360 assert Mailer.deliver_issue_add(issue)
361 assert !last_email.bcc.include?(user.mail)
361 assert !last_email.bcc.include?(user.mail)
362 end
362 end
363
363
364 def test_issue_add_should_include_enabled_fields
364 def test_issue_add_should_include_enabled_fields
365 issue = Issue.find(2)
365 issue = Issue.find(2)
366 assert Mailer.deliver_issue_add(issue)
366 assert Mailer.deliver_issue_add(issue)
367 assert_mail_body_match '* Target version: 1.0', last_email
367 assert_mail_body_match '* Target version: 1.0', last_email
368 assert_select_email do
368 assert_select_email do
369 assert_select 'li', :text => 'Target version: 1.0'
369 assert_select 'li', :text => 'Target version: 1.0'
370 end
370 end
371 end
371 end
372
372
373 def test_issue_add_should_not_include_disabled_fields
373 def test_issue_add_should_not_include_disabled_fields
374 issue = Issue.find(2)
374 issue = Issue.find(2)
375 tracker = issue.tracker
375 tracker = issue.tracker
376 tracker.core_fields -= ['fixed_version_id']
376 tracker.core_fields -= ['fixed_version_id']
377 tracker.save!
377 tracker.save!
378 assert Mailer.deliver_issue_add(issue)
378 assert Mailer.deliver_issue_add(issue)
379 assert_mail_body_no_match 'Target version', last_email
379 assert_mail_body_no_match 'Target version', last_email
380 assert_select_email do
380 assert_select_email do
381 assert_select 'li', :text => /Target version/, :count => 0
381 assert_select 'li', :text => /Target version/, :count => 0
382 end
382 end
383 end
383 end
384
384
385 # test mailer methods for each language
385 # test mailer methods for each language
386 def test_issue_add
386 def test_issue_add
387 issue = Issue.find(1)
387 issue = Issue.find(1)
388 with_each_language_as_default do
388 with_each_language_as_default do
389 assert Mailer.deliver_issue_add(issue)
389 assert Mailer.deliver_issue_add(issue)
390 end
390 end
391 end
391 end
392
392
393 def test_issue_edit
393 def test_issue_edit
394 journal = Journal.find(1)
394 journal = Journal.find(1)
395 with_each_language_as_default do
395 with_each_language_as_default do
396 assert Mailer.deliver_issue_edit(journal)
396 assert Mailer.deliver_issue_edit(journal)
397 end
397 end
398 end
398 end
399
399
400 def test_issue_edit_should_send_private_notes_to_users_with_permission_only
400 def test_issue_edit_should_send_private_notes_to_users_with_permission_only
401 journal = Journal.find(1)
401 journal = Journal.find(1)
402 journal.private_notes = true
402 journal.private_notes = true
403 journal.save!
403 journal.save!
404
404
405 Role.find(2).add_permission! :view_private_notes
405 Role.find(2).add_permission! :view_private_notes
406 Mailer.deliver_issue_edit(journal)
406 Mailer.deliver_issue_edit(journal)
407 assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort
407 assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort
408
408
409 Role.find(2).remove_permission! :view_private_notes
409 Role.find(2).remove_permission! :view_private_notes
410 Mailer.deliver_issue_edit(journal)
410 Mailer.deliver_issue_edit(journal)
411 assert_equal %w(jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort
411 assert_equal %w(jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort
412 end
412 end
413
413
414 def test_issue_edit_should_send_private_notes_to_watchers_with_permission_only
414 def test_issue_edit_should_send_private_notes_to_watchers_with_permission_only
415 Issue.find(1).set_watcher(User.find_by_login('someone'))
415 Issue.find(1).set_watcher(User.find_by_login('someone'))
416 journal = Journal.find(1)
416 journal = Journal.find(1)
417 journal.private_notes = true
417 journal.private_notes = true
418 journal.save!
418 journal.save!
419
419
420 Role.non_member.add_permission! :view_private_notes
420 Role.non_member.add_permission! :view_private_notes
421 Mailer.deliver_issue_edit(journal)
421 Mailer.deliver_issue_edit(journal)
422 assert_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort
422 assert_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort
423
423
424 Role.non_member.remove_permission! :view_private_notes
424 Role.non_member.remove_permission! :view_private_notes
425 Mailer.deliver_issue_edit(journal)
425 Mailer.deliver_issue_edit(journal)
426 assert_not_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort
426 assert_not_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort
427 end
427 end
428
428
429 def test_issue_edit_should_mark_private_notes
429 def test_issue_edit_should_mark_private_notes
430 journal = Journal.find(2)
430 journal = Journal.find(2)
431 journal.private_notes = true
431 journal.private_notes = true
432 journal.save!
432 journal.save!
433
433
434 with_settings :default_language => 'en' do
434 with_settings :default_language => 'en' do
435 Mailer.deliver_issue_edit(journal)
435 Mailer.deliver_issue_edit(journal)
436 end
436 end
437 assert_mail_body_match '(Private notes)', last_email
437 assert_mail_body_match '(Private notes)', last_email
438 end
438 end
439
439
440 def test_issue_edit_with_relation_should_notify_users_who_can_see_the_related_issue
440 def test_issue_edit_with_relation_should_notify_users_who_can_see_the_related_issue
441 issue = Issue.generate!
441 issue = Issue.generate!
442 issue.init_journal(User.find(1))
442 issue.init_journal(User.find(1))
443 private_issue = Issue.generate!(:is_private => true)
443 private_issue = Issue.generate!(:is_private => true)
444 IssueRelation.create!(:issue_from => issue, :issue_to => private_issue, :relation_type => 'relates')
444 IssueRelation.create!(:issue_from => issue, :issue_to => private_issue, :relation_type => 'relates')
445 issue.reload
445 issue.reload
446 assert_equal 1, issue.journals.size
446 assert_equal 1, issue.journals.size
447 journal = issue.journals.first
447 journal = issue.journals.first
448 ActionMailer::Base.deliveries.clear
448 ActionMailer::Base.deliveries.clear
449
449
450 Mailer.deliver_issue_edit(journal)
450 Mailer.deliver_issue_edit(journal)
451 last_email.bcc.each do |email|
451 last_email.bcc.each do |email|
452 user = User.find_by_mail(email)
452 user = User.find_by_mail(email)
453 assert private_issue.visible?(user), "Issue was not visible to #{user}"
453 assert private_issue.visible?(user), "Issue was not visible to #{user}"
454 end
454 end
455 end
455 end
456
456
457 def test_document_added
457 def test_document_added
458 document = Document.find(1)
458 document = Document.find(1)
459 with_each_language_as_default do
459 with_each_language_as_default do
460 assert Mailer.document_added(document).deliver
460 assert Mailer.document_added(document).deliver
461 end
461 end
462 end
462 end
463
463
464 def test_attachments_added
464 def test_attachments_added
465 attachements = [ Attachment.find_by_container_type('Document') ]
465 attachements = [ Attachment.find_by_container_type('Document') ]
466 with_each_language_as_default do
466 with_each_language_as_default do
467 assert Mailer.attachments_added(attachements).deliver
467 assert Mailer.attachments_added(attachements).deliver
468 end
468 end
469 end
469 end
470
470
471 def test_version_file_added
471 def test_version_file_added
472 attachements = [ Attachment.find_by_container_type('Version') ]
472 attachements = [ Attachment.find_by_container_type('Version') ]
473 assert Mailer.attachments_added(attachements).deliver
473 assert Mailer.attachments_added(attachements).deliver
474 assert_not_nil last_email.bcc
474 assert_not_nil last_email.bcc
475 assert last_email.bcc.any?
475 assert last_email.bcc.any?
476 assert_select_email do
476 assert_select_email do
477 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
477 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
478 end
478 end
479 end
479 end
480
480
481 def test_project_file_added
481 def test_project_file_added
482 attachements = [ Attachment.find_by_container_type('Project') ]
482 attachements = [ Attachment.find_by_container_type('Project') ]
483 assert Mailer.attachments_added(attachements).deliver
483 assert Mailer.attachments_added(attachements).deliver
484 assert_not_nil last_email.bcc
484 assert_not_nil last_email.bcc
485 assert last_email.bcc.any?
485 assert last_email.bcc.any?
486 assert_select_email do
486 assert_select_email do
487 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
487 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
488 end
488 end
489 end
489 end
490
490
491 def test_news_added
491 def test_news_added
492 news = News.first
492 news = News.first
493 with_each_language_as_default do
493 with_each_language_as_default do
494 assert Mailer.news_added(news).deliver
494 assert Mailer.news_added(news).deliver
495 end
495 end
496 end
496 end
497
497
498 def test_news_added_should_notify_project_news_watchers
498 def test_news_added_should_notify_project_news_watchers
499 user1 = User.generate!
499 user1 = User.generate!
500 user2 = User.generate!
500 user2 = User.generate!
501 news = News.find(1)
501 news = News.find(1)
502 news.project.enabled_module('news').add_watcher(user1)
502 news.project.enabled_module('news').add_watcher(user1)
503
503
504 Mailer.news_added(news).deliver
504 Mailer.news_added(news).deliver
505 assert_include user1.mail, last_email.bcc
505 assert_include user1.mail, last_email.bcc
506 assert_not_include user2.mail, last_email.bcc
506 assert_not_include user2.mail, last_email.bcc
507 end
507 end
508
508
509 def test_news_comment_added
509 def test_news_comment_added
510 comment = Comment.find(2)
510 comment = Comment.find(2)
511 with_each_language_as_default do
511 with_each_language_as_default do
512 assert Mailer.news_comment_added(comment).deliver
512 assert Mailer.news_comment_added(comment).deliver
513 end
513 end
514 end
514 end
515
515
516 def test_message_posted
516 def test_message_posted
517 message = Message.first
517 message = Message.first
518 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
518 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
519 recipients = recipients.compact.uniq
519 recipients = recipients.compact.uniq
520 with_each_language_as_default do
520 with_each_language_as_default do
521 assert Mailer.message_posted(message).deliver
521 assert Mailer.message_posted(message).deliver
522 end
522 end
523 end
523 end
524
524
525 def test_wiki_content_added
525 def test_wiki_content_added
526 content = WikiContent.find(1)
526 content = WikiContent.find(1)
527 with_each_language_as_default do
527 with_each_language_as_default do
528 assert_difference 'ActionMailer::Base.deliveries.size' do
528 assert_difference 'ActionMailer::Base.deliveries.size' do
529 assert Mailer.wiki_content_added(content).deliver
529 assert Mailer.wiki_content_added(content).deliver
530 assert_select_email do
530 assert_select_email do
531 assert_select 'a[href=?]',
531 assert_select 'a[href=?]',
532 'http://mydomain.foo/projects/ecookbook/wiki/CookBook_documentation',
532 'http://mydomain.foo/projects/ecookbook/wiki/CookBook_documentation',
533 :text => 'CookBook documentation'
533 :text => 'CookBook documentation'
534 end
534 end
535 end
535 end
536 end
536 end
537 end
537 end
538
538
539 def test_wiki_content_updated
539 def test_wiki_content_updated
540 content = WikiContent.find(1)
540 content = WikiContent.find(1)
541 with_each_language_as_default do
541 with_each_language_as_default do
542 assert_difference 'ActionMailer::Base.deliveries.size' do
542 assert_difference 'ActionMailer::Base.deliveries.size' do
543 assert Mailer.wiki_content_updated(content).deliver
543 assert Mailer.wiki_content_updated(content).deliver
544 assert_select_email do
544 assert_select_email do
545 assert_select 'a[href=?]',
545 assert_select 'a[href=?]',
546 'http://mydomain.foo/projects/ecookbook/wiki/CookBook_documentation',
546 'http://mydomain.foo/projects/ecookbook/wiki/CookBook_documentation',
547 :text => 'CookBook documentation'
547 :text => 'CookBook documentation'
548 end
548 end
549 end
549 end
550 end
550 end
551 end
551 end
552
552
553 def test_account_information
553 def test_account_information
554 user = User.find(2)
554 user = User.find(2)
555 valid_languages.each do |lang|
555 valid_languages.each do |lang|
556 user.update_attribute :language, lang.to_s
556 user.update_attribute :language, lang.to_s
557 user.reload
557 user.reload
558 assert Mailer.account_information(user, 'pAsswORd').deliver
558 assert Mailer.account_information(user, 'pAsswORd').deliver
559 end
559 end
560 end
560 end
561
561
562 def test_lost_password
562 def test_lost_password
563 token = Token.find(2)
563 token = Token.find(2)
564 valid_languages.each do |lang|
564 valid_languages.each do |lang|
565 token.user.update_attribute :language, lang.to_s
565 token.user.update_attribute :language, lang.to_s
566 token.reload
566 token.reload
567 assert Mailer.lost_password(token).deliver
567 assert Mailer.lost_password(token).deliver
568 end
568 end
569 end
569 end
570
570
571 def test_register
571 def test_register
572 token = Token.find(1)
572 token = Token.find(1)
573 Setting.host_name = 'redmine.foo'
573 Setting.host_name = 'redmine.foo'
574 Setting.protocol = 'https'
574 Setting.protocol = 'https'
575
575
576 valid_languages.each do |lang|
576 valid_languages.each do |lang|
577 token.user.update_attribute :language, lang.to_s
577 token.user.update_attribute :language, lang.to_s
578 token.reload
578 token.reload
579 ActionMailer::Base.deliveries.clear
579 ActionMailer::Base.deliveries.clear
580 assert Mailer.register(token).deliver
580 assert Mailer.register(token).deliver
581 mail = last_email
581 mail = last_email
582 assert_select_email do
582 assert_select_email do
583 assert_select "a[href=?]",
583 assert_select "a[href=?]",
584 "https://redmine.foo/account/activate?token=#{token.value}",
584 "https://redmine.foo/account/activate?token=#{token.value}",
585 :text => "https://redmine.foo/account/activate?token=#{token.value}"
585 :text => "https://redmine.foo/account/activate?token=#{token.value}"
586 end
586 end
587 end
587 end
588 end
588 end
589
589
590 def test_test
590 def test_test
591 user = User.find(1)
591 user = User.find(1)
592 valid_languages.each do |lang|
592 valid_languages.each do |lang|
593 user.update_attribute :language, lang.to_s
593 user.update_attribute :language, lang.to_s
594 assert Mailer.test_email(user).deliver
594 assert Mailer.test_email(user).deliver
595 end
595 end
596 end
596 end
597
597
598 def test_reminders
598 def test_reminders
599 Mailer.reminders(:days => 42)
599 Mailer.reminders(:days => 42)
600 assert_equal 1, ActionMailer::Base.deliveries.size
600 assert_equal 1, ActionMailer::Base.deliveries.size
601 mail = last_email
601 mail = last_email
602 assert mail.bcc.include?('dlopper@somenet.foo')
602 assert mail.bcc.include?('dlopper@somenet.foo')
603 assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail
603 assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail
604 assert_equal '1 issue(s) due in the next 42 days', mail.subject
604 assert_equal '1 issue(s) due in the next 42 days', mail.subject
605 end
605 end
606
606
607 def test_reminders_should_not_include_closed_issues
607 def test_reminders_should_not_include_closed_issues
608 with_settings :default_language => 'en' do
608 with_settings :default_language => 'en' do
609 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 5,
609 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 5,
610 :subject => 'Closed issue', :assigned_to_id => 3,
610 :subject => 'Closed issue', :assigned_to_id => 3,
611 :due_date => 5.days.from_now,
611 :due_date => 5.days.from_now,
612 :author_id => 2)
612 :author_id => 2)
613 ActionMailer::Base.deliveries.clear
613 ActionMailer::Base.deliveries.clear
614
614
615 Mailer.reminders(:days => 42)
615 Mailer.reminders(:days => 42)
616 assert_equal 1, ActionMailer::Base.deliveries.size
616 assert_equal 1, ActionMailer::Base.deliveries.size
617 mail = last_email
617 mail = last_email
618 assert mail.bcc.include?('dlopper@somenet.foo')
618 assert mail.bcc.include?('dlopper@somenet.foo')
619 assert_mail_body_no_match 'Closed issue', mail
619 assert_mail_body_no_match 'Closed issue', mail
620 end
620 end
621 end
621 end
622
622
623 def test_reminders_for_users
623 def test_reminders_for_users
624 Mailer.reminders(:days => 42, :users => ['5'])
624 Mailer.reminders(:days => 42, :users => ['5'])
625 assert_equal 0, ActionMailer::Base.deliveries.size # No mail for dlopper
625 assert_equal 0, ActionMailer::Base.deliveries.size # No mail for dlopper
626 Mailer.reminders(:days => 42, :users => ['3'])
626 Mailer.reminders(:days => 42, :users => ['3'])
627 assert_equal 1, ActionMailer::Base.deliveries.size # No mail for dlopper
627 assert_equal 1, ActionMailer::Base.deliveries.size # No mail for dlopper
628 mail = last_email
628 mail = last_email
629 assert mail.bcc.include?('dlopper@somenet.foo')
629 assert mail.bcc.include?('dlopper@somenet.foo')
630 assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail
630 assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail
631 end
631 end
632
632
633 def test_reminder_should_include_issues_assigned_to_groups
633 def test_reminder_should_include_issues_assigned_to_groups
634 with_settings :default_language => 'en' do
634 with_settings :default_language => 'en' do
635 group = Group.generate!
635 group = Group.generate!
636 group.users << User.find(2)
636 group.users << User.find(2)
637 group.users << User.find(3)
637 group.users << User.find(3)
638
638
639 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1,
639 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1,
640 :subject => 'Assigned to group', :assigned_to => group,
640 :subject => 'Assigned to group', :assigned_to => group,
641 :due_date => 5.days.from_now,
641 :due_date => 5.days.from_now,
642 :author_id => 2)
642 :author_id => 2)
643 ActionMailer::Base.deliveries.clear
643 ActionMailer::Base.deliveries.clear
644
644
645 Mailer.reminders(:days => 7)
645 Mailer.reminders(:days => 7)
646 assert_equal 2, ActionMailer::Base.deliveries.size
646 assert_equal 2, ActionMailer::Base.deliveries.size
647 assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.map(&:bcc).flatten.sort
647 assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.map(&:bcc).flatten.sort
648 ActionMailer::Base.deliveries.each do |mail|
648 ActionMailer::Base.deliveries.each do |mail|
649 assert_mail_body_match 'Assigned to group', mail
649 assert_mail_body_match 'Assigned to group', mail
650 end
650 end
651 end
651 end
652 end
652 end
653
653
654 def test_reminders_with_version_option
654 def test_reminders_with_version_option
655 with_settings :default_language => 'en' do
655 with_settings :default_language => 'en' do
656 version = Version.generate!(:name => 'Acme', :project_id => 1)
656 version = Version.generate!(:name => 'Acme', :project_id => 1)
657 Issue.generate!(:assigned_to => User.find(2), :due_date => 5.days.from_now)
657 Issue.generate!(:assigned_to => User.find(2), :due_date => 5.days.from_now)
658 Issue.generate!(:assigned_to => User.find(3), :due_date => 5.days.from_now, :fixed_version => version)
658 Issue.generate!(:assigned_to => User.find(3), :due_date => 5.days.from_now, :fixed_version => version)
659 ActionMailer::Base.deliveries.clear
659 ActionMailer::Base.deliveries.clear
660
660
661 Mailer.reminders(:days => 42, :version => 'acme')
661 Mailer.reminders(:days => 42, :version => 'acme')
662 assert_equal 1, ActionMailer::Base.deliveries.size
662 assert_equal 1, ActionMailer::Base.deliveries.size
663
663
664 mail = last_email
664 mail = last_email
665 assert mail.bcc.include?('dlopper@somenet.foo')
665 assert mail.bcc.include?('dlopper@somenet.foo')
666 end
666 end
667 end
667 end
668
668
669 def test_mailer_should_not_change_locale
669 def test_mailer_should_not_change_locale
670 # Set current language to italian
670 # Set current language to italian
671 set_language_if_valid 'it'
671 set_language_if_valid 'it'
672 # Send an email to a french user
672 # Send an email to a french user
673 user = User.find(1)
673 user = User.find(1)
674 user.language = 'fr'
674 user.language = 'fr'
675 Mailer.account_activated(user).deliver
675 Mailer.account_activated(user).deliver
676 mail = last_email
676 mail = last_email
677 assert_mail_body_match 'Votre compte', mail
677 assert_mail_body_match 'Votre compte', mail
678
678
679 assert_equal :it, current_language
679 assert_equal :it, current_language
680 end
680 end
681
681
682 def test_with_deliveries_off
682 def test_with_deliveries_off
683 Mailer.with_deliveries false do
683 Mailer.with_deliveries false do
684 Mailer.test_email(User.find(1)).deliver
684 Mailer.test_email(User.find(1)).deliver
685 end
685 end
686 assert ActionMailer::Base.deliveries.empty?
686 assert ActionMailer::Base.deliveries.empty?
687 # should restore perform_deliveries
687 # should restore perform_deliveries
688 assert ActionMailer::Base.perform_deliveries
688 assert ActionMailer::Base.perform_deliveries
689 end
689 end
690
690
691 def test_token_for_should_strip_trailing_gt_from_address_with_full_name
691 def test_token_for_should_strip_trailing_gt_from_address_with_full_name
692 with_settings :mail_from => "Redmine Mailer<no-reply@redmine.org>" do
692 with_settings :mail_from => "Redmine Mailer<no-reply@redmine.org>" do
693 assert_match /\Aredmine.issue-\d+\.\d+\.[0-9a-f]+@redmine.org\z/, Mailer.token_for(Issue.generate!)
693 assert_match /\Aredmine.issue-\d+\.\d+\.[0-9a-f]+@redmine.org\z/, Mailer.token_for(Issue.generate!)
694 end
694 end
695 end
695 end
696
696
697 def test_layout_should_include_the_emails_header
697 def test_layout_should_include_the_emails_header
698 with_settings :emails_header => "*Header content*" do
698 with_settings :emails_header => "*Header content*" do
699 with_settings :plain_text_mail => 0 do
699 with_settings :plain_text_mail => 0 do
700 assert Mailer.test_email(User.find(1)).deliver
700 assert Mailer.test_email(User.find(1)).deliver
701 assert_select_email do
701 assert_select_email do
702 assert_select ".header" do
702 assert_select ".header" do
703 assert_select "strong", :text => "Header content"
703 assert_select "strong", :text => "Header content"
704 end
704 end
705 end
705 end
706 end
706 end
707 with_settings :plain_text_mail => 1 do
707 with_settings :plain_text_mail => 1 do
708 assert Mailer.test_email(User.find(1)).deliver
708 assert Mailer.test_email(User.find(1)).deliver
709 mail = last_email
709 mail = last_email
710 assert_not_nil mail
710 assert_not_nil mail
711 assert_include "*Header content*", mail.body.decoded
711 assert_include "*Header content*", mail.body.decoded
712 end
712 end
713 end
713 end
714 end
714 end
715
715
716 def test_layout_should_not_include_empty_emails_header
716 def test_layout_should_not_include_empty_emails_header
717 with_settings :emails_header => "", :plain_text_mail => 0 do
717 with_settings :emails_header => "", :plain_text_mail => 0 do
718 assert Mailer.test_email(User.find(1)).deliver
718 assert Mailer.test_email(User.find(1)).deliver
719 assert_select_email do
719 assert_select_email do
720 assert_select ".header", false
720 assert_select ".header", false
721 end
721 end
722 end
722 end
723 end
723 end
724
724
725 def test_layout_should_include_the_emails_footer
725 def test_layout_should_include_the_emails_footer
726 with_settings :emails_footer => "*Footer content*" do
726 with_settings :emails_footer => "*Footer content*" do
727 with_settings :plain_text_mail => 0 do
727 with_settings :plain_text_mail => 0 do
728 assert Mailer.test_email(User.find(1)).deliver
728 assert Mailer.test_email(User.find(1)).deliver
729 assert_select_email do
729 assert_select_email do
730 assert_select ".footer" do
730 assert_select ".footer" do
731 assert_select "strong", :text => "Footer content"
731 assert_select "strong", :text => "Footer content"
732 end
732 end
733 end
733 end
734 end
734 end
735 with_settings :plain_text_mail => 1 do
735 with_settings :plain_text_mail => 1 do
736 assert Mailer.test_email(User.find(1)).deliver
736 assert Mailer.test_email(User.find(1)).deliver
737 mail = last_email
737 mail = last_email
738 assert_not_nil mail
738 assert_not_nil mail
739 assert_include "\n-- \n", mail.body.decoded
739 assert_include "\n-- \n", mail.body.decoded
740 assert_include "*Footer content*", mail.body.decoded
740 assert_include "*Footer content*", mail.body.decoded
741 end
741 end
742 end
742 end
743 end
743 end
744
744
745 def test_layout_should_not_include_empty_emails_footer
745 def test_layout_should_not_include_empty_emails_footer
746 with_settings :emails_footer => "" do
746 with_settings :emails_footer => "" do
747 with_settings :plain_text_mail => 0 do
747 with_settings :plain_text_mail => 0 do
748 assert Mailer.test_email(User.find(1)).deliver
748 assert Mailer.test_email(User.find(1)).deliver
749 assert_select_email do
749 assert_select_email do
750 assert_select ".footer", false
750 assert_select ".footer", false
751 end
751 end
752 end
752 end
753 with_settings :plain_text_mail => 1 do
753 with_settings :plain_text_mail => 1 do
754 assert Mailer.test_email(User.find(1)).deliver
754 assert Mailer.test_email(User.find(1)).deliver
755 mail = last_email
755 mail = last_email
756 assert_not_nil mail
756 assert_not_nil mail
757 assert_not_include "\n-- \n", mail.body.decoded
757 assert_not_include "\n-- \n", mail.body.decoded
758 end
758 end
759 end
759 end
760 end
760 end
761
761
762 def test_should_escape_html_templates_only
762 def test_should_escape_html_templates_only
763 Issue.generate!(:project_id => 1, :tracker_id => 1, :subject => 'Subject with a <tag>')
763 Issue.generate!(:project_id => 1, :tracker_id => 1, :subject => 'Subject with a <tag>')
764 mail = last_email
764 mail = last_email
765 assert_equal 2, mail.parts.size
765 assert_equal 2, mail.parts.size
766 assert_include '<tag>', text_part.body.encoded
766 assert_include '<tag>', text_part.body.encoded
767 assert_include '&lt;tag&gt;', html_part.body.encoded
767 assert_include '&lt;tag&gt;', html_part.body.encoded
768 end
768 end
769
769
770 def test_should_raise_delivery_errors_when_raise_delivery_errors_is_true
770 def test_should_raise_delivery_errors_when_raise_delivery_errors_is_true
771 mail = Mailer.test_email(User.find(1))
771 mail = Mailer.test_email(User.find(1))
772 mail.delivery_method.stubs(:deliver!).raises(Exception.new("delivery error"))
772 mail.delivery_method.stubs(:deliver!).raises(Exception.new("delivery error"))
773
773
774 ActionMailer::Base.raise_delivery_errors = true
774 ActionMailer::Base.raise_delivery_errors = true
775 assert_raise Exception, "delivery error" do
775 assert_raise Exception, "delivery error" do
776 mail.deliver
776 mail.deliver
777 end
777 end
778 ensure
778 ensure
779 ActionMailer::Base.raise_delivery_errors = false
779 ActionMailer::Base.raise_delivery_errors = false
780 end
780 end
781
781
782 def test_should_log_delivery_errors_when_raise_delivery_errors_is_false
782 def test_should_log_delivery_errors_when_raise_delivery_errors_is_false
783 mail = Mailer.test_email(User.find(1))
783 mail = Mailer.test_email(User.find(1))
784 mail.delivery_method.stubs(:deliver!).raises(Exception.new("delivery error"))
784 mail.delivery_method.stubs(:deliver!).raises(Exception.new("delivery error"))
785
785
786 Rails.logger.expects(:error).with("Email delivery error: delivery error")
786 Rails.logger.expects(:error).with("Email delivery error: delivery error")
787 ActionMailer::Base.raise_delivery_errors = false
787 ActionMailer::Base.raise_delivery_errors = false
788 assert_nothing_raised do
788 assert_nothing_raised do
789 mail.deliver
789 mail.deliver
790 end
790 end
791 end
791 end
792
792
793 def test_with_synched_deliveries_should_yield_with_synced_deliveries
793 def test_with_synched_deliveries_should_yield_with_synced_deliveries
794 ActionMailer::Base.delivery_method = :async_smtp
794 ActionMailer::Base.delivery_method = :async_smtp
795 ActionMailer::Base.async_smtp_settings = {:foo => 'bar'}
795 ActionMailer::Base.async_smtp_settings = {:foo => 'bar'}
796
796
797 Mailer.with_synched_deliveries do
797 Mailer.with_synched_deliveries do
798 assert_equal :smtp, ActionMailer::Base.delivery_method
798 assert_equal :smtp, ActionMailer::Base.delivery_method
799 assert_equal({:foo => 'bar'}, ActionMailer::Base.smtp_settings)
799 assert_equal({:foo => 'bar'}, ActionMailer::Base.smtp_settings)
800 end
800 end
801 assert_equal :async_smtp, ActionMailer::Base.delivery_method
801 assert_equal :async_smtp, ActionMailer::Base.delivery_method
802 ensure
802 ensure
803 ActionMailer::Base.delivery_method = :test
803 ActionMailer::Base.delivery_method = :test
804 end
804 end
805
805
806 def test_email_addresses_should_keep_addresses
806 def test_email_addresses_should_keep_addresses
807 assert_equal ["foo@example.net"],
807 assert_equal ["foo@example.net"],
808 Mailer.email_addresses("foo@example.net")
808 Mailer.email_addresses("foo@example.net")
809
809
810 assert_equal ["foo@example.net", "bar@example.net"],
810 assert_equal ["foo@example.net", "bar@example.net"],
811 Mailer.email_addresses(["foo@example.net", "bar@example.net"])
811 Mailer.email_addresses(["foo@example.net", "bar@example.net"])
812 end
812 end
813
813
814 def test_email_addresses_should_replace_users_with_their_email_addresses
814 def test_email_addresses_should_replace_users_with_their_email_addresses
815 assert_equal ["admin@somenet.foo"],
815 assert_equal ["admin@somenet.foo"],
816 Mailer.email_addresses(User.find(1))
816 Mailer.email_addresses(User.find(1))
817
817
818 assert_equal ["admin@somenet.foo", "jsmith@somenet.foo"],
818 assert_equal ["admin@somenet.foo", "jsmith@somenet.foo"],
819 Mailer.email_addresses(User.where(:id => [1,2])).sort
819 Mailer.email_addresses(User.where(:id => [1,2])).sort
820 end
820 end
821
821
822 def test_email_addresses_should_include_notified_emails_addresses_only
822 def test_email_addresses_should_include_notified_emails_addresses_only
823 EmailAddress.create!(:user_id => 2, :address => "another@somenet.foo", :notify => false)
823 EmailAddress.create!(:user_id => 2, :address => "another@somenet.foo", :notify => false)
824 EmailAddress.create!(:user_id => 2, :address => "another2@somenet.foo")
824 EmailAddress.create!(:user_id => 2, :address => "another2@somenet.foo")
825
825
826 assert_equal ["another2@somenet.foo", "jsmith@somenet.foo"],
826 assert_equal ["another2@somenet.foo", "jsmith@somenet.foo"],
827 Mailer.email_addresses(User.find(2)).sort
827 Mailer.email_addresses(User.find(2)).sort
828 end
828 end
829
829
830 private
830 private
831
831
832 def last_email
832 def last_email
833 mail = ActionMailer::Base.deliveries.last
833 mail = ActionMailer::Base.deliveries.last
834 assert_not_nil mail
834 assert_not_nil mail
835 mail
835 mail
836 end
836 end
837
837
838 def text_part
838 def text_part
839 last_email.parts.detect {|part| part.content_type.include?('text/plain')}
839 last_email.parts.detect {|part| part.content_type.include?('text/plain')}
840 end
840 end
841
841
842 def html_part
842 def html_part
843 last_email.parts.detect {|part| part.content_type.include?('text/html')}
843 last_email.parts.detect {|part| part.content_type.include?('text/html')}
844 end
844 end
845
845
846 def with_each_language_as_default(&block)
846 def with_each_language_as_default(&block)
847 valid_languages.each do |lang|
847 valid_languages.each do |lang|
848 with_settings :default_language => lang.to_s do
848 with_settings :default_language => lang.to_s do
849 yield lang
849 yield lang
850 end
850 end
851 end
851 end
852 end
852 end
853 end
853 end
General Comments 0
You need to be logged in to leave comments. Login now