##// END OF EJS Templates
remove ineffective "logger.info" and "logger.error" check from MailHandler model (#14598)...
Toshi MARUYAMA -
r11843:7711e37da4f4
parent child
Show More
@@ -1,517 +1,517
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 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.dup
28 @@handler_options = options.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') if email.respond_to?(:force_encoding)
45 email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding)
46 super(email)
46 super(email)
47 end
47 end
48
48
49 # Extracts MailHandler options from environment variables
49 # Extracts MailHandler options from environment variables
50 # Use when receiving emails with rake tasks
50 # Use when receiving emails with rake tasks
51 def self.extract_options_from_env(env)
51 def self.extract_options_from_env(env)
52 options = {:issue => {}}
52 options = {:issue => {}}
53 %w(project status tracker category priority).each do |option|
53 %w(project status tracker category priority).each do |option|
54 options[:issue][option.to_sym] = env[option] if env[option]
54 options[:issue][option.to_sym] = env[option] if env[option]
55 end
55 end
56 %w(allow_override unknown_user no_permission_check no_account_notice default_group).each do |option|
56 %w(allow_override unknown_user no_permission_check no_account_notice default_group).each do |option|
57 options[option.to_sym] = env[option] if env[option]
57 options[option.to_sym] = env[option] if env[option]
58 end
58 end
59 options
59 options
60 end
60 end
61
61
62 def logger
62 def logger
63 Rails.logger
63 Rails.logger
64 end
64 end
65
65
66 cattr_accessor :ignored_emails_headers
66 cattr_accessor :ignored_emails_headers
67 @@ignored_emails_headers = {
67 @@ignored_emails_headers = {
68 'X-Auto-Response-Suppress' => 'oof',
68 'X-Auto-Response-Suppress' => 'oof',
69 'Auto-Submitted' => /^auto-/
69 'Auto-Submitted' => /^auto-/
70 }
70 }
71
71
72 # Processes incoming emails
72 # Processes incoming emails
73 # Returns the created object (eg. an issue, a message) or false
73 # Returns the created object (eg. an issue, a message) or false
74 def receive(email)
74 def receive(email)
75 @email = email
75 @email = email
76 sender_email = email.from.to_a.first.to_s.strip
76 sender_email = email.from.to_a.first.to_s.strip
77 # Ignore emails received from the application emission address to avoid hell cycles
77 # Ignore emails received from the application emission address to avoid hell cycles
78 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
78 if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
79 if logger && logger.info
79 if logger
80 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
80 logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
81 end
81 end
82 return false
82 return false
83 end
83 end
84 # Ignore auto generated emails
84 # Ignore auto generated emails
85 self.class.ignored_emails_headers.each do |key, ignored_value|
85 self.class.ignored_emails_headers.each do |key, ignored_value|
86 value = email.header[key]
86 value = email.header[key]
87 if value
87 if value
88 value = value.to_s.downcase
88 value = value.to_s.downcase
89 if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value
89 if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value
90 if logger && logger.info
90 if logger
91 logger.info "MailHandler: ignoring email with #{key}:#{value} header"
91 logger.info "MailHandler: ignoring email with #{key}:#{value} header"
92 end
92 end
93 return false
93 return false
94 end
94 end
95 end
95 end
96 end
96 end
97 @user = User.find_by_mail(sender_email) if sender_email.present?
97 @user = User.find_by_mail(sender_email) if sender_email.present?
98 if @user && !@user.active?
98 if @user && !@user.active?
99 if logger && logger.info
99 if logger
100 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]"
100 logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]"
101 end
101 end
102 return false
102 return false
103 end
103 end
104 if @user.nil?
104 if @user.nil?
105 # Email was submitted by an unknown user
105 # Email was submitted by an unknown user
106 case @@handler_options[:unknown_user]
106 case @@handler_options[:unknown_user]
107 when 'accept'
107 when 'accept'
108 @user = User.anonymous
108 @user = User.anonymous
109 when 'create'
109 when 'create'
110 @user = create_user_from_email
110 @user = create_user_from_email
111 if @user
111 if @user
112 if logger && logger.info
112 if logger
113 logger.info "MailHandler: [#{@user.login}] account created"
113 logger.info "MailHandler: [#{@user.login}] account created"
114 end
114 end
115 add_user_to_group(@@handler_options[:default_group])
115 add_user_to_group(@@handler_options[:default_group])
116 unless @@handler_options[:no_account_notice]
116 unless @@handler_options[:no_account_notice]
117 Mailer.account_information(@user, @user.password).deliver
117 Mailer.account_information(@user, @user.password).deliver
118 end
118 end
119 else
119 else
120 if logger && logger.error
120 if logger
121 logger.error "MailHandler: could not create account for [#{sender_email}]"
121 logger.error "MailHandler: could not create account for [#{sender_email}]"
122 end
122 end
123 return false
123 return false
124 end
124 end
125 else
125 else
126 # Default behaviour, emails from unknown users are ignored
126 # Default behaviour, emails from unknown users are ignored
127 if logger && logger.info
127 if logger
128 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]"
128 logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]"
129 end
129 end
130 return false
130 return false
131 end
131 end
132 end
132 end
133 User.current = @user
133 User.current = @user
134 dispatch
134 dispatch
135 end
135 end
136
136
137 private
137 private
138
138
139 MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+(\.[a-f0-9]+)?@}
139 MESSAGE_ID_RE = %r{^<?redmine\.([a-z0-9_]+)\-(\d+)\.\d+(\.[a-f0-9]+)?@}
140 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
140 ISSUE_REPLY_SUBJECT_RE = %r{\[[^\]]*#(\d+)\]}
141 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
141 MESSAGE_REPLY_SUBJECT_RE = %r{\[[^\]]*msg(\d+)\]}
142
142
143 def dispatch
143 def dispatch
144 headers = [email.in_reply_to, email.references].flatten.compact
144 headers = [email.in_reply_to, email.references].flatten.compact
145 subject = email.subject.to_s
145 subject = email.subject.to_s
146 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
146 if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
147 klass, object_id = $1, $2.to_i
147 klass, object_id = $1, $2.to_i
148 method_name = "receive_#{klass}_reply"
148 method_name = "receive_#{klass}_reply"
149 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
149 if self.class.private_instance_methods.collect(&:to_s).include?(method_name)
150 send method_name, object_id
150 send method_name, object_id
151 else
151 else
152 # ignoring it
152 # ignoring it
153 end
153 end
154 elsif m = subject.match(ISSUE_REPLY_SUBJECT_RE)
154 elsif m = subject.match(ISSUE_REPLY_SUBJECT_RE)
155 receive_issue_reply(m[1].to_i)
155 receive_issue_reply(m[1].to_i)
156 elsif m = subject.match(MESSAGE_REPLY_SUBJECT_RE)
156 elsif m = subject.match(MESSAGE_REPLY_SUBJECT_RE)
157 receive_message_reply(m[1].to_i)
157 receive_message_reply(m[1].to_i)
158 else
158 else
159 dispatch_to_default
159 dispatch_to_default
160 end
160 end
161 rescue ActiveRecord::RecordInvalid => e
161 rescue ActiveRecord::RecordInvalid => e
162 # TODO: send a email to the user
162 # TODO: send a email to the user
163 logger.error e.message if logger
163 logger.error e.message if logger
164 false
164 false
165 rescue MissingInformation => e
165 rescue MissingInformation => e
166 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
166 logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
167 false
167 false
168 rescue UnauthorizedAction => e
168 rescue UnauthorizedAction => e
169 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
169 logger.error "MailHandler: unauthorized attempt from #{user}" if logger
170 false
170 false
171 end
171 end
172
172
173 def dispatch_to_default
173 def dispatch_to_default
174 receive_issue
174 receive_issue
175 end
175 end
176
176
177 # Creates a new issue
177 # Creates a new issue
178 def receive_issue
178 def receive_issue
179 project = target_project
179 project = target_project
180 # check permission
180 # check permission
181 unless @@handler_options[:no_permission_check]
181 unless @@handler_options[:no_permission_check]
182 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
182 raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
183 end
183 end
184
184
185 issue = Issue.new(:author => user, :project => project)
185 issue = Issue.new(:author => user, :project => project)
186 issue.safe_attributes = issue_attributes_from_keywords(issue)
186 issue.safe_attributes = issue_attributes_from_keywords(issue)
187 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
187 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
188 issue.subject = cleaned_up_subject
188 issue.subject = cleaned_up_subject
189 if issue.subject.blank?
189 if issue.subject.blank?
190 issue.subject = '(no subject)'
190 issue.subject = '(no subject)'
191 end
191 end
192 issue.description = cleaned_up_text_body
192 issue.description = cleaned_up_text_body
193
193
194 # add To and Cc as watchers before saving so the watchers can reply to Redmine
194 # add To and Cc as watchers before saving so the watchers can reply to Redmine
195 add_watchers(issue)
195 add_watchers(issue)
196 issue.save!
196 issue.save!
197 add_attachments(issue)
197 add_attachments(issue)
198 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
198 logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger
199 issue
199 issue
200 end
200 end
201
201
202 # Adds a note to an existing issue
202 # Adds a note to an existing issue
203 def receive_issue_reply(issue_id, from_journal=nil)
203 def receive_issue_reply(issue_id, from_journal=nil)
204 issue = Issue.find_by_id(issue_id)
204 issue = Issue.find_by_id(issue_id)
205 return unless issue
205 return unless issue
206 # check permission
206 # check permission
207 unless @@handler_options[:no_permission_check]
207 unless @@handler_options[:no_permission_check]
208 unless user.allowed_to?(:add_issue_notes, issue.project) ||
208 unless user.allowed_to?(:add_issue_notes, issue.project) ||
209 user.allowed_to?(:edit_issues, issue.project)
209 user.allowed_to?(:edit_issues, issue.project)
210 raise UnauthorizedAction
210 raise UnauthorizedAction
211 end
211 end
212 end
212 end
213
213
214 # ignore CLI-supplied defaults for new issues
214 # ignore CLI-supplied defaults for new issues
215 @@handler_options[:issue].clear
215 @@handler_options[:issue].clear
216
216
217 journal = issue.init_journal(user)
217 journal = issue.init_journal(user)
218 if from_journal && from_journal.private_notes?
218 if from_journal && from_journal.private_notes?
219 # If the received email was a reply to a private note, make the added note private
219 # If the received email was a reply to a private note, make the added note private
220 issue.private_notes = true
220 issue.private_notes = true
221 end
221 end
222 issue.safe_attributes = issue_attributes_from_keywords(issue)
222 issue.safe_attributes = issue_attributes_from_keywords(issue)
223 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
223 issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
224 journal.notes = cleaned_up_text_body
224 journal.notes = cleaned_up_text_body
225 add_attachments(issue)
225 add_attachments(issue)
226 issue.save!
226 issue.save!
227 if logger && logger.info
227 if logger
228 logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
228 logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
229 end
229 end
230 journal
230 journal
231 end
231 end
232
232
233 # Reply will be added to the issue
233 # Reply will be added to the issue
234 def receive_journal_reply(journal_id)
234 def receive_journal_reply(journal_id)
235 journal = Journal.find_by_id(journal_id)
235 journal = Journal.find_by_id(journal_id)
236 if journal && journal.journalized_type == 'Issue'
236 if journal && journal.journalized_type == 'Issue'
237 receive_issue_reply(journal.journalized_id, journal)
237 receive_issue_reply(journal.journalized_id, journal)
238 end
238 end
239 end
239 end
240
240
241 # Receives a reply to a forum message
241 # Receives a reply to a forum message
242 def receive_message_reply(message_id)
242 def receive_message_reply(message_id)
243 message = Message.find_by_id(message_id)
243 message = Message.find_by_id(message_id)
244 if message
244 if message
245 message = message.root
245 message = message.root
246
246
247 unless @@handler_options[:no_permission_check]
247 unless @@handler_options[:no_permission_check]
248 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
248 raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project)
249 end
249 end
250
250
251 if !message.locked?
251 if !message.locked?
252 reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip,
252 reply = Message.new(:subject => cleaned_up_subject.gsub(%r{^.*msg\d+\]}, '').strip,
253 :content => cleaned_up_text_body)
253 :content => cleaned_up_text_body)
254 reply.author = user
254 reply.author = user
255 reply.board = message.board
255 reply.board = message.board
256 message.children << reply
256 message.children << reply
257 add_attachments(reply)
257 add_attachments(reply)
258 reply
258 reply
259 else
259 else
260 if logger && logger.info
260 if logger
261 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
261 logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
262 end
262 end
263 end
263 end
264 end
264 end
265 end
265 end
266
266
267 def add_attachments(obj)
267 def add_attachments(obj)
268 if email.attachments && email.attachments.any?
268 if email.attachments && email.attachments.any?
269 email.attachments.each do |attachment|
269 email.attachments.each do |attachment|
270 obj.attachments << Attachment.create(:container => obj,
270 obj.attachments << Attachment.create(:container => obj,
271 :file => attachment.decoded,
271 :file => attachment.decoded,
272 :filename => attachment.filename,
272 :filename => attachment.filename,
273 :author => user,
273 :author => user,
274 :content_type => attachment.mime_type)
274 :content_type => attachment.mime_type)
275 end
275 end
276 end
276 end
277 end
277 end
278
278
279 # Adds To and Cc as watchers of the given object if the sender has the
279 # Adds To and Cc as watchers of the given object if the sender has the
280 # appropriate permission
280 # appropriate permission
281 def add_watchers(obj)
281 def add_watchers(obj)
282 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
282 if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
283 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
283 addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
284 unless addresses.empty?
284 unless addresses.empty?
285 watchers = User.active.where('LOWER(mail) IN (?)', addresses).all
285 watchers = User.active.where('LOWER(mail) IN (?)', addresses).all
286 watchers.each {|w| obj.add_watcher(w)}
286 watchers.each {|w| obj.add_watcher(w)}
287 end
287 end
288 end
288 end
289 end
289 end
290
290
291 def get_keyword(attr, options={})
291 def get_keyword(attr, options={})
292 @keywords ||= {}
292 @keywords ||= {}
293 if @keywords.has_key?(attr)
293 if @keywords.has_key?(attr)
294 @keywords[attr]
294 @keywords[attr]
295 else
295 else
296 @keywords[attr] = begin
296 @keywords[attr] = begin
297 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) &&
297 if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) &&
298 (v = extract_keyword!(plain_text_body, attr, options[:format]))
298 (v = extract_keyword!(plain_text_body, attr, options[:format]))
299 v
299 v
300 elsif !@@handler_options[:issue][attr].blank?
300 elsif !@@handler_options[:issue][attr].blank?
301 @@handler_options[:issue][attr]
301 @@handler_options[:issue][attr]
302 end
302 end
303 end
303 end
304 end
304 end
305 end
305 end
306
306
307 # Destructively extracts the value for +attr+ in +text+
307 # Destructively extracts the value for +attr+ in +text+
308 # Returns nil if no matching keyword found
308 # Returns nil if no matching keyword found
309 def extract_keyword!(text, attr, format=nil)
309 def extract_keyword!(text, attr, format=nil)
310 keys = [attr.to_s.humanize]
310 keys = [attr.to_s.humanize]
311 if attr.is_a?(Symbol)
311 if attr.is_a?(Symbol)
312 if user && user.language.present?
312 if user && user.language.present?
313 keys << l("field_#{attr}", :default => '', :locale => user.language)
313 keys << l("field_#{attr}", :default => '', :locale => user.language)
314 end
314 end
315 if Setting.default_language.present?
315 if Setting.default_language.present?
316 keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
316 keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
317 end
317 end
318 end
318 end
319 keys.reject! {|k| k.blank?}
319 keys.reject! {|k| k.blank?}
320 keys.collect! {|k| Regexp.escape(k)}
320 keys.collect! {|k| Regexp.escape(k)}
321 format ||= '.+'
321 format ||= '.+'
322 keyword = nil
322 keyword = nil
323 regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i
323 regexp = /^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i
324 if m = text.match(regexp)
324 if m = text.match(regexp)
325 keyword = m[2].strip
325 keyword = m[2].strip
326 text.gsub!(regexp, '')
326 text.gsub!(regexp, '')
327 end
327 end
328 keyword
328 keyword
329 end
329 end
330
330
331 def target_project
331 def target_project
332 # TODO: other ways to specify project:
332 # TODO: other ways to specify project:
333 # * parse the email To field
333 # * parse the email To field
334 # * specific project (eg. Setting.mail_handler_target_project)
334 # * specific project (eg. Setting.mail_handler_target_project)
335 target = Project.find_by_identifier(get_keyword(:project))
335 target = Project.find_by_identifier(get_keyword(:project))
336 if target.nil?
336 if target.nil?
337 # Invalid project keyword, use the project specified as the default one
337 # Invalid project keyword, use the project specified as the default one
338 default_project = @@handler_options[:issue][:project]
338 default_project = @@handler_options[:issue][:project]
339 if default_project.present?
339 if default_project.present?
340 target = Project.find_by_identifier(default_project)
340 target = Project.find_by_identifier(default_project)
341 end
341 end
342 end
342 end
343 raise MissingInformation.new('Unable to determine target project') if target.nil?
343 raise MissingInformation.new('Unable to determine target project') if target.nil?
344 target
344 target
345 end
345 end
346
346
347 # Returns a Hash of issue attributes extracted from keywords in the email body
347 # Returns a Hash of issue attributes extracted from keywords in the email body
348 def issue_attributes_from_keywords(issue)
348 def issue_attributes_from_keywords(issue)
349 assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue)
349 assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue)
350
350
351 attrs = {
351 attrs = {
352 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
352 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
353 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),
353 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),
354 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
354 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
355 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
355 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
356 'assigned_to_id' => assigned_to.try(:id),
356 'assigned_to_id' => assigned_to.try(:id),
357 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) &&
357 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) &&
358 issue.project.shared_versions.named(k).first.try(:id),
358 issue.project.shared_versions.named(k).first.try(:id),
359 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
359 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
360 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
360 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
361 'estimated_hours' => get_keyword(:estimated_hours, :override => true),
361 'estimated_hours' => get_keyword(:estimated_hours, :override => true),
362 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0')
362 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0')
363 }.delete_if {|k, v| v.blank? }
363 }.delete_if {|k, v| v.blank? }
364
364
365 if issue.new_record? && attrs['tracker_id'].nil?
365 if issue.new_record? && attrs['tracker_id'].nil?
366 attrs['tracker_id'] = issue.project.trackers.first.try(:id)
366 attrs['tracker_id'] = issue.project.trackers.first.try(:id)
367 end
367 end
368
368
369 attrs
369 attrs
370 end
370 end
371
371
372 # Returns a Hash of issue custom field values extracted from keywords in the email body
372 # Returns a Hash of issue custom field values extracted from keywords in the email body
373 def custom_field_values_from_keywords(customized)
373 def custom_field_values_from_keywords(customized)
374 customized.custom_field_values.inject({}) do |h, v|
374 customized.custom_field_values.inject({}) do |h, v|
375 if keyword = get_keyword(v.custom_field.name, :override => true)
375 if keyword = get_keyword(v.custom_field.name, :override => true)
376 h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized)
376 h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(keyword, customized)
377 end
377 end
378 h
378 h
379 end
379 end
380 end
380 end
381
381
382 # Returns the text/plain part of the email
382 # Returns the text/plain part of the email
383 # If not found (eg. HTML-only email), returns the body with tags removed
383 # If not found (eg. HTML-only email), returns the body with tags removed
384 def plain_text_body
384 def plain_text_body
385 return @plain_text_body unless @plain_text_body.nil?
385 return @plain_text_body unless @plain_text_body.nil?
386
386
387 parts = if (text_parts = email.all_parts.select {|p| p.mime_type == 'text/plain'}).present?
387 parts = if (text_parts = email.all_parts.select {|p| p.mime_type == 'text/plain'}).present?
388 text_parts
388 text_parts
389 elsif (html_parts = email.all_parts.select {|p| p.mime_type == 'text/html'}).present?
389 elsif (html_parts = email.all_parts.select {|p| p.mime_type == 'text/html'}).present?
390 html_parts
390 html_parts
391 else
391 else
392 [email]
392 [email]
393 end
393 end
394 @plain_text_body = parts.map {|p| Redmine::CodesetUtil.to_utf8(p.body.decoded, p.charset)}.join("\r\n")
394 @plain_text_body = parts.map {|p| Redmine::CodesetUtil.to_utf8(p.body.decoded, p.charset)}.join("\r\n")
395
395
396 # strip html tags and remove doctype directive
396 # strip html tags and remove doctype directive
397 if parts.any? {|p| p.mime_type == 'text/html'}
397 if parts.any? {|p| p.mime_type == 'text/html'}
398 @plain_text_body = strip_tags(@plain_text_body.strip)
398 @plain_text_body = strip_tags(@plain_text_body.strip)
399 @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
399 @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
400 end
400 end
401
401
402 @plain_text_body
402 @plain_text_body
403 end
403 end
404
404
405 def cleaned_up_text_body
405 def cleaned_up_text_body
406 cleanup_body(plain_text_body)
406 cleanup_body(plain_text_body)
407 end
407 end
408
408
409 def cleaned_up_subject
409 def cleaned_up_subject
410 subject = email.subject.to_s
410 subject = email.subject.to_s
411 subject.strip[0,255]
411 subject.strip[0,255]
412 end
412 end
413
413
414 def self.full_sanitizer
414 def self.full_sanitizer
415 @full_sanitizer ||= HTML::FullSanitizer.new
415 @full_sanitizer ||= HTML::FullSanitizer.new
416 end
416 end
417
417
418 def self.assign_string_attribute_with_limit(object, attribute, value, limit=nil)
418 def self.assign_string_attribute_with_limit(object, attribute, value, limit=nil)
419 limit ||= object.class.columns_hash[attribute.to_s].limit || 255
419 limit ||= object.class.columns_hash[attribute.to_s].limit || 255
420 value = value.to_s.slice(0, limit)
420 value = value.to_s.slice(0, limit)
421 object.send("#{attribute}=", value)
421 object.send("#{attribute}=", value)
422 end
422 end
423
423
424 # Returns a User from an email address and a full name
424 # Returns a User from an email address and a full name
425 def self.new_user_from_attributes(email_address, fullname=nil)
425 def self.new_user_from_attributes(email_address, fullname=nil)
426 user = User.new
426 user = User.new
427
427
428 # Truncating the email address would result in an invalid format
428 # Truncating the email address would result in an invalid format
429 user.mail = email_address
429 user.mail = email_address
430 assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
430 assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
431
431
432 names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
432 names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
433 assign_string_attribute_with_limit(user, 'firstname', names.shift, 30)
433 assign_string_attribute_with_limit(user, 'firstname', names.shift, 30)
434 assign_string_attribute_with_limit(user, 'lastname', names.join(' '), 30)
434 assign_string_attribute_with_limit(user, 'lastname', names.join(' '), 30)
435 user.lastname = '-' if user.lastname.blank?
435 user.lastname = '-' if user.lastname.blank?
436 user.language = Setting.default_language
436 user.language = Setting.default_language
437 user.generate_password = true
437 user.generate_password = true
438 user.mail_notification = 'only_my_events'
438 user.mail_notification = 'only_my_events'
439
439
440 unless user.valid?
440 unless user.valid?
441 user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
441 user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
442 user.firstname = "-" unless user.errors[:firstname].blank?
442 user.firstname = "-" unless user.errors[:firstname].blank?
443 (puts user.errors[:lastname];user.lastname = "-") unless user.errors[:lastname].blank?
443 (puts user.errors[:lastname];user.lastname = "-") unless user.errors[:lastname].blank?
444 end
444 end
445
445
446 user
446 user
447 end
447 end
448
448
449 # Creates a User for the +email+ sender
449 # Creates a User for the +email+ sender
450 # Returns the user or nil if it could not be created
450 # Returns the user or nil if it could not be created
451 def create_user_from_email
451 def create_user_from_email
452 from = email.header['from'].to_s
452 from = email.header['from'].to_s
453 addr, name = from, nil
453 addr, name = from, nil
454 if m = from.match(/^"?(.+?)"?\s+<(.+@.+)>$/)
454 if m = from.match(/^"?(.+?)"?\s+<(.+@.+)>$/)
455 addr, name = m[2], m[1]
455 addr, name = m[2], m[1]
456 end
456 end
457 if addr.present?
457 if addr.present?
458 user = self.class.new_user_from_attributes(addr, name)
458 user = self.class.new_user_from_attributes(addr, name)
459 if @@handler_options[:no_notification]
459 if @@handler_options[:no_notification]
460 user.mail_notification = 'none'
460 user.mail_notification = 'none'
461 end
461 end
462 if user.save
462 if user.save
463 user
463 user
464 else
464 else
465 logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger
465 logger.error "MailHandler: failed to create User: #{user.errors.full_messages}" if logger
466 nil
466 nil
467 end
467 end
468 else
468 else
469 logger.error "MailHandler: failed to create User: no FROM address found" if logger
469 logger.error "MailHandler: failed to create User: no FROM address found" if logger
470 nil
470 nil
471 end
471 end
472 end
472 end
473
473
474 # Adds the newly created user to default group
474 # Adds the newly created user to default group
475 def add_user_to_group(default_group)
475 def add_user_to_group(default_group)
476 if default_group.present?
476 if default_group.present?
477 default_group.split(',').each do |group_name|
477 default_group.split(',').each do |group_name|
478 if group = Group.named(group_name).first
478 if group = Group.named(group_name).first
479 group.users << @user
479 group.users << @user
480 elsif logger
480 elsif logger
481 logger.warn "MailHandler: could not add user to [#{group_name}], group not found"
481 logger.warn "MailHandler: could not add user to [#{group_name}], group not found"
482 end
482 end
483 end
483 end
484 end
484 end
485 end
485 end
486
486
487 # Removes the email body of text after the truncation configurations.
487 # Removes the email body of text after the truncation configurations.
488 def cleanup_body(body)
488 def cleanup_body(body)
489 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
489 delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
490 unless delimiters.empty?
490 unless delimiters.empty?
491 regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
491 regex = Regexp.new("^[> ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE)
492 body = body.gsub(regex, '')
492 body = body.gsub(regex, '')
493 end
493 end
494 body.strip
494 body.strip
495 end
495 end
496
496
497 def find_assignee_from_keyword(keyword, issue)
497 def find_assignee_from_keyword(keyword, issue)
498 keyword = keyword.to_s.downcase
498 keyword = keyword.to_s.downcase
499 assignable = issue.assignable_users
499 assignable = issue.assignable_users
500 assignee = nil
500 assignee = nil
501 assignee ||= assignable.detect {|a|
501 assignee ||= assignable.detect {|a|
502 a.mail.to_s.downcase == keyword ||
502 a.mail.to_s.downcase == keyword ||
503 a.login.to_s.downcase == keyword
503 a.login.to_s.downcase == keyword
504 }
504 }
505 if assignee.nil? && keyword.match(/ /)
505 if assignee.nil? && keyword.match(/ /)
506 firstname, lastname = *(keyword.split) # "First Last Throwaway"
506 firstname, lastname = *(keyword.split) # "First Last Throwaway"
507 assignee ||= assignable.detect {|a|
507 assignee ||= assignable.detect {|a|
508 a.is_a?(User) && a.firstname.to_s.downcase == firstname &&
508 a.is_a?(User) && a.firstname.to_s.downcase == firstname &&
509 a.lastname.to_s.downcase == lastname
509 a.lastname.to_s.downcase == lastname
510 }
510 }
511 end
511 end
512 if assignee.nil?
512 if assignee.nil?
513 assignee ||= assignable.detect {|a| a.name.downcase == keyword}
513 assignee ||= assignable.detect {|a| a.name.downcase == keyword}
514 end
514 end
515 assignee
515 assignee
516 end
516 end
517 end
517 end
General Comments 0
You need to be logged in to leave comments. Login now